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/logger.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Redis
|
4
|
+
class Logger < ::Logger
|
5
|
+
|
6
|
+
# Redis levels are: DEBUG < INFO < NOTICE < WARNING
|
7
|
+
# This logger inserts support for NOTICE
|
8
|
+
|
9
|
+
def initialize(logdev, *args)
|
10
|
+
super
|
11
|
+
@raw_logdev = logdev
|
12
|
+
@default_formatter = proc { |severity, datetime, progname, msg|
|
13
|
+
mark = case severity
|
14
|
+
when 'DEBUG' then '.'
|
15
|
+
when 'INFO' then '-'
|
16
|
+
when 'NOTE' then '*'
|
17
|
+
when 'WARN' then '#'
|
18
|
+
else '!'
|
19
|
+
end
|
20
|
+
"[#{Process.pid}] #{datetime.strftime '%d %b %H:%H:%S'} #{mark} #{msg}\n"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def flush
|
25
|
+
@raw_logdev.flush if @raw_logdev.respond_to? :flush
|
26
|
+
end
|
27
|
+
|
28
|
+
module Severity
|
29
|
+
# logger.rb says "max 5 char" for labels
|
30
|
+
SEV_LABEL = %w(DEBUG INFO NOTE WARN ERROR FATAL ANY)
|
31
|
+
DEBUG = 0
|
32
|
+
INFO = 1
|
33
|
+
NOTICE = 2
|
34
|
+
WARN = 3
|
35
|
+
ERROR = 4
|
36
|
+
FATAL = 5
|
37
|
+
UNKNOWN = 6
|
38
|
+
end
|
39
|
+
include Severity
|
40
|
+
|
41
|
+
def notice(progname = nil, &block)
|
42
|
+
add(NOTICE, nil, progname, &block)
|
43
|
+
end
|
44
|
+
def warn(progname = nil, &block)
|
45
|
+
add(WARN, nil, progname, &block)
|
46
|
+
end
|
47
|
+
def error(progname = nil, &block)
|
48
|
+
add(ERROR, nil, progname, &block)
|
49
|
+
end
|
50
|
+
def fatal(progname = nil, &block)
|
51
|
+
add(FATAL, nil, progname, &block)
|
52
|
+
end
|
53
|
+
def unknown(progname = nil, &block)
|
54
|
+
add(UNKNOWN, nil, progname, &block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def notice?; @level <= NOTICE; end
|
58
|
+
def warn?; @level <= WARN; end
|
59
|
+
def error?; @level <= ERROR; end
|
60
|
+
def fatal?; @level <= FATAL; end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def format_severity(severity)
|
65
|
+
SEV_LABEL[severity] || 'ANY'
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Redis
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
def initialize *args
|
5
|
+
@reader = Reader.new
|
6
|
+
@multi = nil
|
7
|
+
@deferred = nil
|
8
|
+
@watcher = nil
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def unbind
|
13
|
+
@deferred.unbind if @deferred
|
14
|
+
@watcher.unbind if @watcher
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# Process incoming redis protocol
|
19
|
+
def receive_data data
|
20
|
+
return unless @reader
|
21
|
+
@reader.feed data
|
22
|
+
until (strings = @reader.gets) == false
|
23
|
+
if @multi and !%w{WATCH DISCARD MULTI EXEC DEBUG}.include?(strings[0].upcase)
|
24
|
+
@multi << strings
|
25
|
+
send_redis :'+QUEUED'
|
26
|
+
else
|
27
|
+
result = __send__ "redis_#{strings[0].upcase}", *strings[1..-1]
|
28
|
+
if Integer === result
|
29
|
+
send_data ":#{result}\r\n"
|
30
|
+
elsif EventMachine::Deferrable === result
|
31
|
+
@deferred.unbind if @deferred and @deferred != result
|
32
|
+
@deferred = result
|
33
|
+
elsif result == :quit
|
34
|
+
@reader = nil
|
35
|
+
close_connection_after_writing
|
36
|
+
break
|
37
|
+
elsif result != :exec
|
38
|
+
send_redis result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
rescue StandardError, LoadError, SyntaxError => e
|
43
|
+
# This sometimes comes in handy for the TCL tests
|
44
|
+
# @logger.warn "#{e.class}:/#{e.backtrace[0]} #{e.message}"
|
45
|
+
# e.backtrace[1..-1].each {|bt| @logger.warn bt}
|
46
|
+
send_data "-ERR #{e.class.name}: #{e.message}\r\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
def redis_WATCH *keys
|
50
|
+
raise 'WATCH inside MULTI is not allowed' if @multi
|
51
|
+
@watcher ||= Database::Watcher.new
|
52
|
+
@watcher.bind @database, *keys
|
53
|
+
:'+OK'
|
54
|
+
end
|
55
|
+
|
56
|
+
def redis_UNWATCH
|
57
|
+
if @watcher
|
58
|
+
@watcher.unbind
|
59
|
+
@watcher = nil
|
60
|
+
end
|
61
|
+
:'+OK'
|
62
|
+
end
|
63
|
+
|
64
|
+
def redis_MULTI
|
65
|
+
raise 'MULTI nesting not allowed' if @multi
|
66
|
+
@multi = []
|
67
|
+
:'+OK'
|
68
|
+
end
|
69
|
+
|
70
|
+
def redis_DISCARD
|
71
|
+
redis_UNWATCH
|
72
|
+
@multi = nil
|
73
|
+
:'+OK'
|
74
|
+
end
|
75
|
+
|
76
|
+
def redis_EXEC
|
77
|
+
if @watcher
|
78
|
+
still_bound = @watcher.bound
|
79
|
+
redis_UNWATCH
|
80
|
+
unless still_bound
|
81
|
+
@multi = nil
|
82
|
+
return :'*-1'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
send_data "*#{@multi.size}\r\n"
|
86
|
+
response = []
|
87
|
+
@multi.each do |strings|
|
88
|
+
begin
|
89
|
+
result = __send__ "redis_#{strings[0].upcase}", *strings[1..-1]
|
90
|
+
rescue StandardError, LoadError, SyntaxError => e
|
91
|
+
result = e
|
92
|
+
end
|
93
|
+
if EventMachine::Deferrable === result
|
94
|
+
result.unbind
|
95
|
+
send_redis nil
|
96
|
+
elsif Exception === result
|
97
|
+
send_data "-ERR #{result.class.name}: #{result.message}"
|
98
|
+
else
|
99
|
+
send_redis result
|
100
|
+
end
|
101
|
+
end
|
102
|
+
@multi = nil
|
103
|
+
:exec
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
data/lib/redis/pubsub.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module Redis
|
4
|
+
module PubSub
|
5
|
+
|
6
|
+
class Subscription
|
7
|
+
include EventMachine::Deferrable
|
8
|
+
|
9
|
+
@@channels ||= {}
|
10
|
+
@@pchannels ||= {}
|
11
|
+
|
12
|
+
def self.publish channel, message
|
13
|
+
subs = @@channels[channel] || []
|
14
|
+
subs.each do |sub|
|
15
|
+
sub.call channel, message
|
16
|
+
end
|
17
|
+
sent = subs.size
|
18
|
+
(@@pchannels || []).each do |pchannel, subs|
|
19
|
+
next unless File.fnmatch(pchannel, channel)
|
20
|
+
subs.each do |sub|
|
21
|
+
sub.pcall pchannel, channel, message
|
22
|
+
end
|
23
|
+
sent += subs.size
|
24
|
+
end
|
25
|
+
sent
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize connection
|
29
|
+
@connection = connection
|
30
|
+
@subs = []
|
31
|
+
@psubs = []
|
32
|
+
end
|
33
|
+
|
34
|
+
def bound
|
35
|
+
size > 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def size
|
39
|
+
@subs.size + @psubs.size
|
40
|
+
end
|
41
|
+
|
42
|
+
def unbind
|
43
|
+
unbind_channels
|
44
|
+
unbind_patterns
|
45
|
+
end
|
46
|
+
|
47
|
+
def unbind_channels
|
48
|
+
@subs.each do |channel|
|
49
|
+
(@@channels[channel] || []).delete self
|
50
|
+
end
|
51
|
+
@subs.clear
|
52
|
+
end
|
53
|
+
|
54
|
+
def unbind_patterns
|
55
|
+
@psubs.each do |channel|
|
56
|
+
(@@pchannels[channel] || []).delete self
|
57
|
+
end
|
58
|
+
@psubs.clear
|
59
|
+
end
|
60
|
+
|
61
|
+
def subscribe channel
|
62
|
+
c = (@@channels[channel] ||= [])
|
63
|
+
unless c.include? self
|
64
|
+
c << self
|
65
|
+
@subs << channel
|
66
|
+
end
|
67
|
+
@connection.send_redis ['subscribe', channel, size]
|
68
|
+
end
|
69
|
+
|
70
|
+
def psubscribe channel
|
71
|
+
c = (@@pchannels[channel] ||= [])
|
72
|
+
unless c.include? self
|
73
|
+
c << self
|
74
|
+
@psubs << channel
|
75
|
+
end
|
76
|
+
@connection.send_redis ['psubscribe', channel, size]
|
77
|
+
end
|
78
|
+
|
79
|
+
def unsubscribe channel
|
80
|
+
c = (@@channels[channel] || [])
|
81
|
+
@subs.delete channel if c.delete self
|
82
|
+
@connection.send_redis ['unsubscribe', channel, size]
|
83
|
+
end
|
84
|
+
|
85
|
+
def punsubscribe channel
|
86
|
+
c = (@@pchannels[channel] || [])
|
87
|
+
@psubs.delete channel if c.delete self
|
88
|
+
@connection.send_redis ['punsubscribe', channel, size]
|
89
|
+
end
|
90
|
+
|
91
|
+
def call channel, message
|
92
|
+
@connection.send_redis ['message', channel, message]
|
93
|
+
end
|
94
|
+
|
95
|
+
def pcall pchannel, channel, message
|
96
|
+
@connection.send_redis ['pmessage', pchannel, channel, message]
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
def redis_SUBSCRIBE *channels
|
102
|
+
sub = @deferred
|
103
|
+
sub = Subscription.new self unless Subscription === sub
|
104
|
+
channels.each do |channel|
|
105
|
+
sub.subscribe channel
|
106
|
+
end
|
107
|
+
sub
|
108
|
+
end
|
109
|
+
|
110
|
+
def redis_UNSUBSCRIBE *channels
|
111
|
+
sub = @deferred
|
112
|
+
sub = Subscription.new self unless Subscription === sub
|
113
|
+
if channels.empty?
|
114
|
+
sub.unbind_channels
|
115
|
+
else
|
116
|
+
channels.each do |channel|
|
117
|
+
sub.unsubscribe channel
|
118
|
+
end
|
119
|
+
end
|
120
|
+
sub
|
121
|
+
end
|
122
|
+
|
123
|
+
def redis_PSUBSCRIBE *channels
|
124
|
+
sub = @deferred
|
125
|
+
sub = Subscription.new self unless Subscription === sub
|
126
|
+
channels.each do |channel|
|
127
|
+
sub.psubscribe channel
|
128
|
+
end
|
129
|
+
sub
|
130
|
+
end
|
131
|
+
|
132
|
+
def redis_PUNSUBSCRIBE *channels
|
133
|
+
sub = @deferred
|
134
|
+
sub = Subscription.new self unless Subscription === sub
|
135
|
+
if channels.empty?
|
136
|
+
sub.unbind_patterns
|
137
|
+
else
|
138
|
+
channels.each do |channel|
|
139
|
+
sub.punsubscribe channel
|
140
|
+
end
|
141
|
+
end
|
142
|
+
sub
|
143
|
+
end
|
144
|
+
|
145
|
+
def redis_PUBLISH channel, message
|
146
|
+
Subscription.publish channel, message
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
data/lib/redis/reader.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
module Redis
|
2
|
+
class Reader < Array
|
3
|
+
|
4
|
+
# Minimize the amount of memory copying. The primary
|
5
|
+
# performance trick is to String#split and work with that.
|
6
|
+
# This is almost as fast as hiredis/reader plus it supports servers
|
7
|
+
|
8
|
+
def initialize options={}
|
9
|
+
super()
|
10
|
+
@multibulk_limit = options[:multibulk_limit] || 1024*1024
|
11
|
+
@bulk_limit = options[:bulk_limit] || 512*1024*1024
|
12
|
+
@buffer_overflow = false
|
13
|
+
flush
|
14
|
+
end
|
15
|
+
|
16
|
+
def feed data
|
17
|
+
return if @buffer_overflow
|
18
|
+
if reduce(0){|t,e|t+e.bytesize} > @bulk_limit + 2
|
19
|
+
@buffer_overflow = true
|
20
|
+
flush
|
21
|
+
return
|
22
|
+
end
|
23
|
+
push data
|
24
|
+
end
|
25
|
+
|
26
|
+
def gets
|
27
|
+
raise 'buffer overflow' if @buffer_overflow
|
28
|
+
if @completed.empty?
|
29
|
+
unshift_split if @split
|
30
|
+
frame do |str|
|
31
|
+
@elements << str
|
32
|
+
if @remaining != 0
|
33
|
+
if @remaining < 0
|
34
|
+
@remaining = -@remaining
|
35
|
+
@elements = str
|
36
|
+
end
|
37
|
+
@remaining -= 1
|
38
|
+
if @remaining == 0 and !@stack.empty?
|
39
|
+
elements = @elements
|
40
|
+
@elements, @remaining = @stack.pop
|
41
|
+
@elements << elements
|
42
|
+
@remaining -= 1
|
43
|
+
end
|
44
|
+
next unless @remaining == 0
|
45
|
+
@completed << @elements
|
46
|
+
elsif !@elements.empty?
|
47
|
+
@completed << @elements[0]
|
48
|
+
end
|
49
|
+
@elements = []
|
50
|
+
@remaining = 0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
return false if @completed.empty?
|
54
|
+
@completed.shift
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def flush
|
60
|
+
@split = nil
|
61
|
+
@pending = nil
|
62
|
+
@binary_size = nil
|
63
|
+
@remaining = 0
|
64
|
+
@elements = []
|
65
|
+
@stack = []
|
66
|
+
@completed = []
|
67
|
+
clear
|
68
|
+
end
|
69
|
+
|
70
|
+
def unshift_split
|
71
|
+
unshift @split.join "\n"
|
72
|
+
@split = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
# yields redis data until no more found in buffer
|
76
|
+
def frame
|
77
|
+
while true
|
78
|
+
if @binary_size
|
79
|
+
s = read_binary @binary_size
|
80
|
+
break unless s
|
81
|
+
@binary_size = nil
|
82
|
+
yield s
|
83
|
+
else
|
84
|
+
line = read_line
|
85
|
+
break unless line
|
86
|
+
case line[0..0]
|
87
|
+
when '-'
|
88
|
+
yield RuntimeError.new line[1..-1]
|
89
|
+
when '+'
|
90
|
+
yield line[1..-1]
|
91
|
+
when ':'
|
92
|
+
yield line[1..-1].to_i
|
93
|
+
when '*'
|
94
|
+
prev_remaining = @remaining
|
95
|
+
@remaining = line[1..-1].to_i
|
96
|
+
if @remaining == 0
|
97
|
+
@remaining = -1
|
98
|
+
yield []
|
99
|
+
elsif @remaining == -1
|
100
|
+
yield nil
|
101
|
+
elsif @remaining > @multibulk_limit
|
102
|
+
flush
|
103
|
+
raise 'invalid multibulk length'
|
104
|
+
elsif prev_remaining > 0
|
105
|
+
@stack << [@elements, prev_remaining]
|
106
|
+
@elements = []
|
107
|
+
end
|
108
|
+
when '$'
|
109
|
+
@binary_size = line[1..-1].to_i
|
110
|
+
if @binary_size == -1
|
111
|
+
@binary_size = nil
|
112
|
+
yield nil
|
113
|
+
elsif (@binary_size == 0 and line[1..1] != '0') or @binary_size < 0 or @binary_size > @bulk_limit
|
114
|
+
flush
|
115
|
+
raise 'invalid bulk length'
|
116
|
+
end
|
117
|
+
else
|
118
|
+
if @remaining > 0
|
119
|
+
flush
|
120
|
+
raise "expected \"$\", got #{line[0].chr.dump}"
|
121
|
+
end
|
122
|
+
parts = line.split(' ')
|
123
|
+
@remaining = parts.size
|
124
|
+
parts.each {|l| yield l}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Read a binary redis token, nil if none available
|
131
|
+
def read_binary length
|
132
|
+
if @split
|
133
|
+
if @split.first.size >= length
|
134
|
+
result = @split.shift[0...length]
|
135
|
+
unshift_split if @split.size == 1
|
136
|
+
return result
|
137
|
+
end
|
138
|
+
unshift_split
|
139
|
+
end
|
140
|
+
unless @pending
|
141
|
+
size = reduce(0){|x,y|x+y.size}
|
142
|
+
return nil unless size >= length
|
143
|
+
@pending = dup
|
144
|
+
clear
|
145
|
+
remainder = size - length
|
146
|
+
if remainder > 0
|
147
|
+
last_string = @pending[-1]
|
148
|
+
@pending[-1] = last_string[0...-remainder]
|
149
|
+
push last_string[-remainder..-1]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
# eat newline
|
153
|
+
return nil unless read_line
|
154
|
+
result = @pending.join
|
155
|
+
@pending = nil
|
156
|
+
result
|
157
|
+
end
|
158
|
+
|
159
|
+
# Read a newline terminated redis token, nil if none available
|
160
|
+
def read_line
|
161
|
+
unless @split
|
162
|
+
@split = join.split "\n", -1
|
163
|
+
clear
|
164
|
+
end
|
165
|
+
if @split.size > 1
|
166
|
+
result = @split.shift.chomp "\n"
|
167
|
+
else
|
168
|
+
result = nil
|
169
|
+
end
|
170
|
+
unshift_split if @split.size == 1
|
171
|
+
result
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|