em-redis 0.1.1 → 0.2
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/.gitignore +2 -0
- data/History.txt +7 -0
- data/README.rdoc +4 -14
- data/Rakefile +17 -11
- data/em-redis.gemspec +27 -15
- data/lib/em-redis.rb +15 -4
- data/lib/em-redis/redis_protocol.rb +273 -344
- data/spec/live_redis_protocol_spec.rb +36 -39
- data/spec/redis_protocol_spec.rb +31 -23
- data/tasks/em-redis.rake +12 -0
- metadata +39 -10
data/.gitignore
ADDED
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
== 0.2 / 2009-12-??
|
2
|
+
* rip off stock redis gem
|
3
|
+
* sort is no longer compatible with 0.1 version
|
4
|
+
* response of exists, sismember, sadd, srem, smove, zadd, zrem, move, setnx, del, renamenx, and expire is either true of false, not 0/1 as in 0.1
|
5
|
+
* info returns hash of symbols now
|
6
|
+
* lrem has different argument order
|
7
|
+
|
1
8
|
== 0.1.1 / 2009-05-01
|
2
9
|
|
3
10
|
* added a number of aliases to redis-based method names
|
data/README.rdoc
CHANGED
@@ -7,25 +7,15 @@ Modeled after eventmachine's implementation of the memcached protocol, and influ
|
|
7
7
|
|
8
8
|
This library is only useful when used as part of an application that relies on
|
9
9
|
Event Machine's event loop. It implements an EM-based client protocol, which
|
10
|
-
leverages the non-blocking nature of the EM interface to
|
10
|
+
leverages the non-blocking nature of the EM interface to achieve significant
|
11
11
|
parallelization without threads.
|
12
12
|
|
13
|
-
WARNING: this library is my first attempt to write an evented client protocol,
|
14
|
-
and isn't currently used in production anywhere. All that bit in the license
|
15
|
-
about not being warranted to work for any particular purpose really applies.
|
16
|
-
|
17
13
|
|
18
14
|
== FEATURES/PROBLEMS:
|
19
15
|
|
20
16
|
* Implements most Redis commands (see {the list of available commands here}[http://code.google.com/p/redis/wiki/CommandReference] with the notable
|
21
17
|
exception of MONITOR and AUTH
|
22
18
|
|
23
|
-
== TODO:
|
24
|
-
|
25
|
-
* Right now method names are identical to Redis command names. I'll add some nice aliases soon.
|
26
|
-
* I'm sure multibulk responses can be handled more elegantly
|
27
|
-
* Better default error handling? Provide a default response block?
|
28
|
-
|
29
19
|
== SYNOPSIS:
|
30
20
|
|
31
21
|
* Like any Deferrable eventmachine-based protocol implementation, using EM-Redis involves making calls and passing blocks that serve as callbacks when
|
@@ -44,7 +34,7 @@ the call returns.
|
|
44
34
|
end
|
45
35
|
end
|
46
36
|
|
47
|
-
* To run live tests on a Redis server (currently compatible with
|
37
|
+
* To run live tests on a Redis server (currently compatible with 1.02)
|
48
38
|
|
49
39
|
rake redis:live_test
|
50
40
|
|
@@ -61,13 +51,13 @@ currently in the form of bacon specs. I'll port them to RSpec at some point.
|
|
61
51
|
|
62
52
|
== INSTALL:
|
63
53
|
|
64
|
-
* sudo gem install
|
54
|
+
* sudo gem install em-redis
|
65
55
|
|
66
56
|
== LICENSE:
|
67
57
|
|
68
58
|
(The MIT License)
|
69
59
|
|
70
|
-
Copyright (c) 2008
|
60
|
+
Copyright (c) 2008, 2009
|
71
61
|
|
72
62
|
Permission is hereby granted, free of charge, to any person obtaining
|
73
63
|
a copy of this software and associated documentation files (the
|
data/Rakefile
CHANGED
@@ -4,23 +4,29 @@
|
|
4
4
|
|
5
5
|
begin
|
6
6
|
require 'bones'
|
7
|
-
Bones.setup
|
8
7
|
rescue LoadError
|
9
|
-
|
8
|
+
abort '### Please install the "bones" gem ###'
|
10
9
|
end
|
11
10
|
|
12
11
|
ensure_in_path 'lib'
|
13
12
|
require 'em-redis'
|
14
13
|
|
15
|
-
task :default => '
|
14
|
+
task :default => ['redis:live_test', 'redis:offline_test']
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
16
|
+
Bones {
|
17
|
+
name 'em-redis'
|
18
|
+
authors 'Jonathan Broad'
|
19
|
+
email 'jonathan@relativepath.org'
|
20
|
+
url ''
|
21
|
+
version EMRedis::VERSION
|
22
|
+
|
23
|
+
readme_file 'README.rdoc'
|
24
|
+
ignore_file '.gitignore'
|
25
|
+
|
26
|
+
depend_on 'eventmachine'
|
27
|
+
|
28
|
+
depend_on "bacon", :development => true
|
29
|
+
depend_on "em-spec", :development => true
|
30
|
+
}
|
25
31
|
|
26
32
|
# EOF
|
data/em-redis.gemspec
CHANGED
@@ -1,35 +1,47 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
Gem::Specification.new do |s|
|
2
4
|
s.name = %q{em-redis}
|
3
|
-
s.version = "0.
|
5
|
+
s.version = "0.2"
|
4
6
|
|
5
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
8
|
s.authors = ["Jonathan Broad"]
|
7
|
-
s.date = %q{2009-
|
8
|
-
s.description = %q{An EventMachine[http://rubyeventmachine.com/] based library for interacting with the very cool Redis[http://code.google.com/p/redis/] data store by Salvatore 'antirez' Sanfilippo.
|
9
|
+
s.date = %q{2009-12-15}
|
10
|
+
s.description = %q{An EventMachine[http://rubyeventmachine.com/] based library for interacting with the very cool Redis[http://code.google.com/p/redis/] data store by Salvatore 'antirez' Sanfilippo.
|
11
|
+
Modeled after eventmachine's implementation of the memcached protocol, and influenced by Ezra Zygmuntowicz's {redis-rb}[http://github.com/ezmobius/redis-rb/tree/master] library (distributed as part of Redis).
|
12
|
+
|
13
|
+
This library is only useful when used as part of an application that relies on
|
14
|
+
Event Machine's event loop. It implements an EM-based client protocol, which
|
15
|
+
leverages the non-blocking nature of the EM interface to achieve significant
|
16
|
+
parallelization without threads.}
|
9
17
|
s.email = %q{jonathan@relativepath.org}
|
10
|
-
s.extra_rdoc_files = ["History.txt", "README.rdoc"]
|
11
|
-
s.files = ["History.txt", "Manifest.txt", "README.rdoc", "em-redis.gemspec", "
|
12
|
-
s.has_rdoc = true
|
18
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.rdoc"]
|
19
|
+
s.files = [".gitignore", "History.txt", "Manifest.txt", "README.rdoc", "Rakefile", "em-redis.gemspec", "lib/em-redis.rb", "lib/em-redis/redis_protocol.rb", "spec/live_redis_protocol_spec.rb", "spec/redis_protocol_spec.rb", "spec/test_helper.rb", "tasks/em-redis.rake"]
|
13
20
|
s.rdoc_options = ["--main", "README.rdoc"]
|
14
21
|
s.require_paths = ["lib"]
|
15
22
|
s.rubyforge_project = %q{em-redis}
|
16
|
-
s.rubygems_version = %q{1.3.
|
23
|
+
s.rubygems_version = %q{1.3.5}
|
17
24
|
s.summary = %q{An EventMachine[http://rubyeventmachine}
|
18
25
|
|
19
26
|
if s.respond_to? :specification_version then
|
20
27
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
21
|
-
s.specification_version =
|
28
|
+
s.specification_version = 3
|
22
29
|
|
23
30
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
24
|
-
s.
|
25
|
-
s.add_development_dependency(%q<
|
31
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.11"])
|
32
|
+
s.add_development_dependency(%q<bacon>, [">= 1.1.0"])
|
33
|
+
s.add_development_dependency(%q<em-spec>, [">= 0.2.0"])
|
34
|
+
s.add_development_dependency(%q<bones>, [">= 3.2.0"])
|
26
35
|
else
|
27
|
-
s.add_dependency(%q<
|
28
|
-
s.add_dependency(%q<
|
36
|
+
s.add_dependency(%q<eventmachine>, [">= 0.12.11"])
|
37
|
+
s.add_dependency(%q<bacon>, [">= 1.1.0"])
|
38
|
+
s.add_dependency(%q<em-spec>, [">= 0.2.0"])
|
39
|
+
s.add_dependency(%q<bones>, [">= 3.2.0"])
|
29
40
|
end
|
30
41
|
else
|
31
|
-
s.add_dependency(%q<
|
32
|
-
s.add_dependency(%q<
|
42
|
+
s.add_dependency(%q<eventmachine>, [">= 0.12.11"])
|
43
|
+
s.add_dependency(%q<bacon>, [">= 1.1.0"])
|
44
|
+
s.add_dependency(%q<em-spec>, [">= 0.2.0"])
|
45
|
+
s.add_dependency(%q<bones>, [">= 3.2.0"])
|
33
46
|
end
|
34
47
|
end
|
35
|
-
|
data/lib/em-redis.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
module EMRedis
|
3
3
|
|
4
4
|
# :stopdoc:
|
5
|
-
VERSION = '0.
|
5
|
+
VERSION = '0.2'
|
6
6
|
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
7
7
|
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
8
8
|
# :startdoc:
|
@@ -29,8 +29,19 @@ module EMRedis
|
|
29
29
|
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
# Utility method used to require all files ending in .rb that lie in the
|
33
|
+
# directory below this file that has the same name as the filename passed
|
34
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
35
|
+
# the _filename_ does not have to be equivalent to the directory.
|
36
|
+
#
|
37
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
38
|
+
dir ||= ::File.basename(fname, '.*')
|
39
|
+
search_me = ::File.expand_path(
|
40
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
33
41
|
|
34
|
-
|
42
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
43
|
+
end
|
44
|
+
|
45
|
+
end # module EMRedis
|
35
46
|
|
36
|
-
|
47
|
+
EMRedis.require_all_libs_relative_to(__FILE__)
|
@@ -10,322 +10,252 @@ module EventMachine
|
|
10
10
|
# constants
|
11
11
|
#########################
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
#SADD,SREM,SCARD,SISMEMBER,SINTER,SINTERSTORE,SUNION,SUNIONSTORE,SMEMBERS
|
172
|
-
def sadd(key, value, &blk) #set_add, add
|
173
|
-
multiline_command "SADD", key, value, &blk
|
174
|
-
end
|
175
|
-
alias_method :set_add, :sadd
|
176
|
-
alias_method :add, :sadd
|
177
|
-
|
178
|
-
def srem(key, value, &blk) #set_remove
|
179
|
-
multiline_command "SREM", key, value, &blk
|
180
|
-
end
|
181
|
-
alias_method :set_remove, :srem
|
182
|
-
|
183
|
-
def scard(key, &blk) # set_size
|
184
|
-
inline_command "SCARD", key, &blk
|
185
|
-
end
|
186
|
-
alias_method :set_size, :scard
|
187
|
-
|
188
|
-
def sismember(key, value, &blk) #set_member? member?
|
189
|
-
multiline_command "SISMEMBER", key, value, &blk
|
190
|
-
end
|
191
|
-
alias_method :set_member?, :sismember
|
192
|
-
alias_method :member?, :sismember
|
193
|
-
|
194
|
-
def sinter(*keys, &blk) #intersect
|
195
|
-
inline_command "SINTER", *keys, &blk
|
196
|
-
end
|
197
|
-
alias_method :intersect, :sinter
|
198
|
-
|
199
|
-
def sinterstore(target_key, *keys, &blk) #intersect_and_store
|
200
|
-
inline_command "SINTERSTORE", target_key, *keys, &blk
|
201
|
-
end
|
202
|
-
alias_method :intersect_and_store, :sinterstore
|
203
|
-
|
204
|
-
# UNION SET MANIP NOT IN RELEASE BUILDS YET
|
205
|
-
############################################
|
206
|
-
#
|
207
|
-
# def sunion(*keys, &blk)
|
208
|
-
# inline_command "SUNION", *keys, &blk
|
209
|
-
# end
|
210
|
-
#
|
211
|
-
# def sunionstore(target_key, *keys, &blk)
|
212
|
-
# inline_command "SUNIONSTORE", target_key, *keys, &blk
|
213
|
-
# end
|
214
|
-
#
|
215
|
-
############################################
|
216
|
-
|
217
|
-
def smembers(key, &blk) #set_members, members
|
218
|
-
inline_command "SMEMBERS", key, &blk
|
219
|
-
end
|
220
|
-
alias_method :set_members, :smembers
|
221
|
-
alias_method :members, :smembers
|
222
|
-
|
223
|
-
|
224
|
-
#SELECT,MOVE,FLUSHDB,FLUSHALL
|
225
|
-
def select(db_index, &blk)
|
226
|
-
inline_command "SELECT", db_index, &blk
|
13
|
+
OK = "OK".freeze
|
14
|
+
MINUS = "-".freeze
|
15
|
+
PLUS = "+".freeze
|
16
|
+
COLON = ":".freeze
|
17
|
+
DOLLAR = "$".freeze
|
18
|
+
ASTERISK = "*".freeze
|
19
|
+
DELIM = "\r\n".freeze
|
20
|
+
|
21
|
+
BULK_COMMANDS = {
|
22
|
+
"set" => true,
|
23
|
+
"setnx" => true,
|
24
|
+
"rpush" => true,
|
25
|
+
"lpush" => true,
|
26
|
+
"lset" => true,
|
27
|
+
"lrem" => true,
|
28
|
+
"sadd" => true,
|
29
|
+
"srem" => true,
|
30
|
+
"sismember" => true,
|
31
|
+
"rpoplpush" => true,
|
32
|
+
"echo" => true,
|
33
|
+
"getset" => true,
|
34
|
+
"smove" => true,
|
35
|
+
"zadd" => true,
|
36
|
+
"zrem" => true,
|
37
|
+
"zscore" => true
|
38
|
+
}
|
39
|
+
|
40
|
+
MULTI_BULK_COMMANDS = {
|
41
|
+
"mset" => true,
|
42
|
+
"msetnx" => true,
|
43
|
+
# these aliases aren't in redis gem
|
44
|
+
"multi_get" => true
|
45
|
+
}
|
46
|
+
|
47
|
+
BOOLEAN_PROCESSOR = lambda{|r| r == 1 }
|
48
|
+
|
49
|
+
REPLY_PROCESSOR = {
|
50
|
+
"exists" => BOOLEAN_PROCESSOR,
|
51
|
+
"sismember" => BOOLEAN_PROCESSOR,
|
52
|
+
"sadd" => BOOLEAN_PROCESSOR,
|
53
|
+
"srem" => BOOLEAN_PROCESSOR,
|
54
|
+
"smove" => BOOLEAN_PROCESSOR,
|
55
|
+
"zadd" => BOOLEAN_PROCESSOR,
|
56
|
+
"zrem" => BOOLEAN_PROCESSOR,
|
57
|
+
"move" => BOOLEAN_PROCESSOR,
|
58
|
+
"setnx" => BOOLEAN_PROCESSOR,
|
59
|
+
"del" => BOOLEAN_PROCESSOR,
|
60
|
+
"renamenx" => BOOLEAN_PROCESSOR,
|
61
|
+
"expire" => BOOLEAN_PROCESSOR,
|
62
|
+
"select" => BOOLEAN_PROCESSOR, # not in redis gem
|
63
|
+
"keys" => lambda{|r| r.split(" ")},
|
64
|
+
"info" => lambda{|r|
|
65
|
+
info = {}
|
66
|
+
r.each_line {|kv|
|
67
|
+
k,v = kv.split(":",2).map{|x| x.chomp}
|
68
|
+
info[k.to_sym] = v
|
69
|
+
}
|
70
|
+
info
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
ALIASES = {
|
75
|
+
"flush_db" => "flushdb",
|
76
|
+
"flush_all" => "flushall",
|
77
|
+
"last_save" => "lastsave",
|
78
|
+
"key?" => "exists",
|
79
|
+
"delete" => "del",
|
80
|
+
"randkey" => "randomkey",
|
81
|
+
"list_length" => "llen",
|
82
|
+
"push_tail" => "rpush",
|
83
|
+
"push_head" => "lpush",
|
84
|
+
"pop_tail" => "rpop",
|
85
|
+
"pop_head" => "lpop",
|
86
|
+
"list_set" => "lset",
|
87
|
+
"list_range" => "lrange",
|
88
|
+
"list_trim" => "ltrim",
|
89
|
+
"list_index" => "lindex",
|
90
|
+
"list_rm" => "lrem",
|
91
|
+
"set_add" => "sadd",
|
92
|
+
"set_delete" => "srem",
|
93
|
+
"set_count" => "scard",
|
94
|
+
"set_member?" => "sismember",
|
95
|
+
"set_members" => "smembers",
|
96
|
+
"set_intersect" => "sinter",
|
97
|
+
"set_intersect_store" => "sinterstore",
|
98
|
+
"set_inter_store" => "sinterstore",
|
99
|
+
"set_union" => "sunion",
|
100
|
+
"set_union_store" => "sunionstore",
|
101
|
+
"set_diff" => "sdiff",
|
102
|
+
"set_diff_store" => "sdiffstore",
|
103
|
+
"set_move" => "smove",
|
104
|
+
"set_unless_exists" => "setnx",
|
105
|
+
"rename_unless_exists" => "renamenx",
|
106
|
+
"type?" => "type",
|
107
|
+
"zset_add" => "zadd",
|
108
|
+
"zset_count" => 'zcard',
|
109
|
+
"zset_range_by_score" => 'zrangebyscore',
|
110
|
+
"zset_reverse_range" => 'zrevrange',
|
111
|
+
"zset_range" => 'zrange',
|
112
|
+
"zset_delete" => 'zrem',
|
113
|
+
"zset_score" => 'zscore',
|
114
|
+
# these aliases aren't in redis gem
|
115
|
+
"background_save" => 'bgsave',
|
116
|
+
"async_save" => 'bgsave',
|
117
|
+
"members" => 'smembers',
|
118
|
+
"decrement_by" => "decrby",
|
119
|
+
"decrement" => "decr",
|
120
|
+
"increment_by" => "incrby",
|
121
|
+
"increment" => "incr",
|
122
|
+
"set_if_nil" => "setnx",
|
123
|
+
"multi_get" => "mget",
|
124
|
+
"random_key" => "randomkey",
|
125
|
+
"random" => "randomkey",
|
126
|
+
"rename_if_nil" => "renamenx",
|
127
|
+
"tail_pop" => "rpop",
|
128
|
+
"pop" => "rpop",
|
129
|
+
"head_pop" => "lpop",
|
130
|
+
"shift" => "lpop",
|
131
|
+
"list_remove" => "lrem",
|
132
|
+
"index" => "lindex",
|
133
|
+
"trim" => "ltrim",
|
134
|
+
"list_range" => "lrange",
|
135
|
+
"range" => "lrange",
|
136
|
+
"list_len" => "llen",
|
137
|
+
"len" => "llen",
|
138
|
+
"head_push" => "lpush",
|
139
|
+
"unshift" => "lpush",
|
140
|
+
"tail_push" => "rpush",
|
141
|
+
"push" => "rpush",
|
142
|
+
"add" => "sadd",
|
143
|
+
"set_remove" => "srem",
|
144
|
+
"set_size" => "scard",
|
145
|
+
"member?" => "sismember",
|
146
|
+
"intersect" => "sinter",
|
147
|
+
"intersect_and_store" => "sinterstore",
|
148
|
+
"members" => "smembers",
|
149
|
+
"exists?" => "exists"
|
150
|
+
}
|
151
|
+
|
152
|
+
DISABLED_COMMANDS = {
|
153
|
+
"monitor" => true,
|
154
|
+
"sync" => true
|
155
|
+
}
|
156
|
+
|
157
|
+
def [](key)
|
158
|
+
self.get(key)
|
159
|
+
end
|
160
|
+
|
161
|
+
def []=(key,value)
|
162
|
+
set(key,value)
|
163
|
+
end
|
164
|
+
|
165
|
+
def set(key, value, expiry=nil)
|
166
|
+
call_command([:set, key, value]) do |s|
|
167
|
+
expire(key, expiry) if s == OK && expiry
|
168
|
+
yield s if block_given?
|
169
|
+
end
|
227
170
|
end
|
228
171
|
|
229
|
-
def
|
230
|
-
|
172
|
+
def sort(key, options={}, &blk)
|
173
|
+
cmd = ["SORT"]
|
174
|
+
cmd << key
|
175
|
+
cmd << "BY #{options[:by]}" if options[:by]
|
176
|
+
cmd << "GET #{[options[:get]].flatten * ' GET '}" if options[:get]
|
177
|
+
cmd << "#{options[:order]}" if options[:order]
|
178
|
+
cmd << "LIMIT #{options[:limit].join(' ')}" if options[:limit]
|
179
|
+
call_command(cmd, &blk)
|
231
180
|
end
|
232
181
|
|
233
|
-
def
|
234
|
-
|
182
|
+
def incr(key, increment = nil, &blk)
|
183
|
+
call_command(increment ? ["incrby",key,increment] : ["incr",key], &blk)
|
235
184
|
end
|
236
185
|
|
237
|
-
def
|
238
|
-
|
186
|
+
def decr(key, decrement = nil, &blk)
|
187
|
+
call_command(decrement ? ["decrby",key,decrement] : ["decr",key], &blk)
|
239
188
|
end
|
240
189
|
|
241
|
-
#
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
#SAVE,BGSAVE,LASTSAVE,SHUTDOWN
|
253
|
-
def shutdown(&blk)
|
254
|
-
inline_command "SHUTDOWN", &blk
|
190
|
+
# Similar to memcache.rb's #get_multi, returns a hash mapping
|
191
|
+
# keys to values.
|
192
|
+
def mapped_mget(*keys)
|
193
|
+
mget(*keys) do |response|
|
194
|
+
result = {}
|
195
|
+
response.each do |value|
|
196
|
+
key = keys.shift
|
197
|
+
result.merge!(key => value) unless value.nil?
|
198
|
+
end
|
199
|
+
yield result if block_given?
|
200
|
+
end
|
255
201
|
end
|
256
202
|
|
257
|
-
|
258
|
-
|
203
|
+
# Ruby defines a now deprecated type method so we need to override it here
|
204
|
+
# since it will never hit method_missing
|
205
|
+
def type(key, &blk)
|
206
|
+
call_command(['type', key], &blk)
|
259
207
|
end
|
260
208
|
|
261
|
-
def
|
262
|
-
|
209
|
+
def quit(&blk)
|
210
|
+
call_command(['quit'], &blk)
|
263
211
|
end
|
264
|
-
alias_method :background_save, :bgsave
|
265
|
-
alias_method :async_save, :bgsave
|
266
212
|
|
267
|
-
def
|
268
|
-
|
213
|
+
def on_error(&blk)
|
214
|
+
@err_cb = blk
|
269
215
|
end
|
270
216
|
|
271
|
-
|
272
|
-
|
273
|
-
wrapper = lambda do |r|
|
274
|
-
blk.call(
|
275
|
-
r.split.inject({}) {|hash,string| key,value = string.split(":"); hash[key] = value; hash }
|
276
|
-
)
|
277
|
-
end
|
278
|
-
inline_command "INFO", &wrapper
|
279
|
-
end
|
280
|
-
|
281
|
-
# MONITOR's a bit tricky
|
282
|
-
def monitor
|
217
|
+
def method_missing(*argv, &blk)
|
218
|
+
call_command(argv, &blk)
|
283
219
|
end
|
284
220
|
|
285
|
-
def
|
286
|
-
|
221
|
+
def call_command(argv, &blk)
|
222
|
+
callback { raw_call_command(argv, &blk) }
|
287
223
|
end
|
288
224
|
|
225
|
+
def raw_call_command(argv, &blk)
|
226
|
+
argv = argv.dup
|
289
227
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
command += " "
|
301
|
-
command += args.join(" ")
|
228
|
+
if MULTI_BULK_COMMANDS[argv.flatten[0].to_s]
|
229
|
+
# TODO improve this code
|
230
|
+
argvp = argv.flatten
|
231
|
+
values = argvp.pop.to_a.flatten
|
232
|
+
argvp = values.unshift(argvp[0])
|
233
|
+
command = ["*#{argvp.size}"]
|
234
|
+
argvp.each do |v|
|
235
|
+
v = v.to_s
|
236
|
+
command << "$#{get_size(v)}"
|
237
|
+
command << v
|
302
238
|
end
|
303
|
-
command
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
@redis_callbacks << blk
|
314
|
-
command += " "
|
315
|
-
if args.size > 0
|
316
|
-
command += args.join(" ")
|
317
|
-
command += " "
|
239
|
+
command = command.map {|cmd| "#{cmd}\r\n"}.join
|
240
|
+
else
|
241
|
+
command = ""
|
242
|
+
bulk = nil
|
243
|
+
argv[0] = argv[0].to_s.downcase
|
244
|
+
argv[0] = ALIASES[argv[0]] if ALIASES[argv[0]]
|
245
|
+
raise "#{argv[0]} command is disabled" if DISABLED_COMMANDS[argv[0]]
|
246
|
+
if BULK_COMMANDS[argv[0]] and argv.length > 1
|
247
|
+
bulk = argv[-1].to_s
|
248
|
+
argv[-1] = get_size(bulk)
|
318
249
|
end
|
319
|
-
command
|
320
|
-
command
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
250
|
+
command << "#{argv.join(' ')}\r\n"
|
251
|
+
command << "#{bulk}\r\n" if bulk
|
252
|
+
end
|
253
|
+
|
254
|
+
puts "*** sending: #{command}" if $debug
|
255
|
+
@redis_callbacks << [REPLY_PROCESSOR[argv[0]], blk]
|
256
|
+
send_data command
|
326
257
|
end
|
327
258
|
|
328
|
-
|
329
259
|
##
|
330
260
|
# errors
|
331
261
|
#########################
|
@@ -342,12 +272,12 @@ module EventMachine
|
|
342
272
|
# em hooks
|
343
273
|
#########################
|
344
274
|
|
345
|
-
def self.connect
|
275
|
+
def self.connect(host = 'localhost', port = 6379 )
|
346
276
|
puts "*** connecting" if $debug
|
347
277
|
EM.connect host, port, self, host, port
|
348
278
|
end
|
349
279
|
|
350
|
-
def initialize
|
280
|
+
def initialize(host, port = 6379 )
|
351
281
|
puts "*** initializing" if $debug
|
352
282
|
@host, @port = host, port
|
353
283
|
end
|
@@ -366,11 +296,11 @@ module EventMachine
|
|
366
296
|
# 19Feb09 Switched to a custom parser, LineText2 is recursive and can cause
|
367
297
|
# stack overflows when there is too much data.
|
368
298
|
# include EM::P::LineText2
|
369
|
-
def receive_data
|
299
|
+
def receive_data(data)
|
370
300
|
(@buffer||='') << data
|
371
|
-
while index = @buffer.index(
|
301
|
+
while index = @buffer.index(DELIM)
|
372
302
|
begin
|
373
|
-
line = @buffer.slice!(0,index+2)
|
303
|
+
line = @buffer.slice!(0, index+2)
|
374
304
|
process_cmd line
|
375
305
|
rescue ParserError
|
376
306
|
@buffer[0...0] = line
|
@@ -379,53 +309,54 @@ module EventMachine
|
|
379
309
|
end
|
380
310
|
end
|
381
311
|
|
382
|
-
def process_cmd
|
312
|
+
def process_cmd(line)
|
383
313
|
puts "*** processing #{line}" if $debug
|
384
314
|
# first character of buffer will always be the response type
|
385
|
-
reply_type = line[0]
|
315
|
+
reply_type = line[0, 1]
|
386
316
|
reply_args = line.slice(1..-3) # remove type character and \r\n
|
387
317
|
case reply_type
|
388
318
|
|
389
|
-
#
|
390
|
-
when
|
391
|
-
|
392
|
-
|
319
|
+
#e.g. -MISSING
|
320
|
+
when MINUS
|
321
|
+
@redis_callbacks.shift # throw away the cb?
|
322
|
+
if @err_cb
|
323
|
+
@err_cb.call(reply_args)
|
324
|
+
else
|
325
|
+
err = RedisError.new
|
326
|
+
err.code = reply_args
|
327
|
+
raise err, "Redis server returned error code: #{err.code}"
|
393
328
|
end
|
394
329
|
|
330
|
+
# e.g. +OK
|
331
|
+
when PLUS
|
332
|
+
dispatch_response(reply_args)
|
333
|
+
|
395
334
|
# e.g. $3\r\nabc\r\n
|
396
335
|
# 'bulk' is more complex because it could be part of multi-bulk
|
397
|
-
when
|
398
|
-
data_len = Integer(
|
336
|
+
when DOLLAR
|
337
|
+
data_len = Integer(reply_args)
|
399
338
|
if data_len == -1 # expect no data; return nil
|
400
339
|
if @multibulk_n > 0 # we're in the middle of a multibulk reply
|
401
340
|
@values << nil
|
402
341
|
if @values.size == @multibulk_n # DING, we're done
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
@multibulk_n = 0
|
407
|
-
end
|
408
|
-
end
|
409
|
-
else
|
410
|
-
if cb = @redis_callbacks.shift
|
411
|
-
cb.call(nil)
|
342
|
+
dispatch_response(@values)
|
343
|
+
@values = []
|
344
|
+
@multibulk_n = 0
|
412
345
|
end
|
346
|
+
else
|
347
|
+
dispatch_response(nil)
|
413
348
|
end
|
414
349
|
elsif @buffer.size >= data_len + 2 # buffer is full of expected data
|
415
350
|
if @multibulk_n > 0 # we're in the middle of a multibulk reply
|
416
|
-
@values << @buffer.slice!(0,data_len)
|
351
|
+
@values << @buffer.slice!(0, data_len)
|
417
352
|
if @values.size == @multibulk_n # DING, we're done
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
@multibulk_n = 0
|
422
|
-
end
|
353
|
+
dispatch_response(@values)
|
354
|
+
@values = []
|
355
|
+
@multibulk_n = 0
|
423
356
|
end
|
424
357
|
else # not multibulk
|
425
|
-
value = @buffer.slice!(0,data_len)
|
426
|
-
|
427
|
-
cb.call(value)
|
428
|
-
end
|
358
|
+
value = @buffer.slice!(0, data_len)
|
359
|
+
dispatch_response(value)
|
429
360
|
end
|
430
361
|
@buffer.slice!(0,2) # tossing \r\n
|
431
362
|
else # buffer isn't full or nil
|
@@ -433,35 +364,28 @@ module EventMachine
|
|
433
364
|
# more data complete buffer
|
434
365
|
raise ParserError
|
435
366
|
end
|
367
|
+
|
436
368
|
#e.g. :8
|
437
|
-
when
|
438
|
-
|
439
|
-
|
440
|
-
end
|
369
|
+
when COLON
|
370
|
+
dispatch_response(Integer(reply_args))
|
371
|
+
|
441
372
|
#e.g. *2\r\n$1\r\na\r\n$1\r\nb\r\n
|
442
|
-
when
|
373
|
+
when ASTERISK
|
443
374
|
@multibulk_n = Integer(reply_args)
|
444
|
-
if @multibulk_n == -1
|
445
|
-
|
446
|
-
cb.call(nil)
|
447
|
-
end
|
448
|
-
end
|
449
|
-
#e.g. -MISSING
|
450
|
-
when C_ERR
|
451
|
-
@redis_callbacks.shift # throw away the cb?
|
452
|
-
if @err_cb
|
453
|
-
@err_cb.call(reply_args)
|
454
|
-
else
|
455
|
-
err = RedisError.new
|
456
|
-
err.code = reply_args
|
457
|
-
raise err, "Redis server returned error code: #{err.code}"
|
458
|
-
end
|
375
|
+
dispatch_response(nil) if @multibulk_n == -1
|
376
|
+
|
459
377
|
# Whu?
|
460
378
|
else
|
461
379
|
raise ProtocolError, "reply type not recognized: #{line.strip}"
|
462
380
|
end
|
463
381
|
end
|
464
382
|
|
383
|
+
def dispatch_response(value)
|
384
|
+
processor, blk = @redis_callbacks.shift
|
385
|
+
value = processor.call(value) if processor
|
386
|
+
blk.call(value) if blk
|
387
|
+
end
|
388
|
+
|
465
389
|
def unbind
|
466
390
|
puts "*** unbinding" if $debug
|
467
391
|
if @connected or @reconnecting
|
@@ -470,10 +394,15 @@ module EventMachine
|
|
470
394
|
@reconnecting = true
|
471
395
|
@deferred_status = nil
|
472
396
|
else
|
473
|
-
raise 'Unable to connect to
|
397
|
+
raise 'Unable to connect to redis server'
|
474
398
|
end
|
475
399
|
end
|
476
400
|
|
401
|
+
private
|
402
|
+
def get_size(string)
|
403
|
+
string.respond_to?(:bytesize) ? string.bytesize : string.size
|
404
|
+
end
|
405
|
+
|
477
406
|
end
|
478
407
|
end
|
479
408
|
end
|