em-redis 0.1.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ pkg
@@ -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
@@ -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 acheive significant
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 0.91)
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 madsimian-em-redis --source http://gems.github.com
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
- load 'tasks/setup.rb'
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 => 'spec:run'
14
+ task :default => ['redis:live_test', 'redis:offline_test']
16
15
 
17
- PROJ.name = 'em-redis'
18
- PROJ.authors = 'Jonathan Broad'
19
- PROJ.email = 'jonathan@relativepath.org'
20
- PROJ.url = ''
21
- PROJ.version = EMRedis::VERSION
22
- PROJ.rubyforge.name = 'em-redis'
23
- PROJ.spec.opts << '--color'
24
- PROJ.gem.dependencies << "bacon"
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
@@ -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.1.1"
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-04-28}
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. 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). This library is only useful when used as part of an application that relies on Event Machine's event loop. It implements an EM-based client protocol, which leverages the non-blocking nature of the EM interface to acheive significant parallelization without threads. WARNING: this library is my first attempt to write an evented client protocol, and isn't currently used in production anywhere. All that bit in the license about not being warranted to work for any particular purpose really applies.}
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", "Rakefile", "lib/em-redis.rb", "lib/em-redis/redis_protocol.rb", "spec/test_helper.rb", "spec/live_redis_protocol_spec.rb", "spec/redis_protocol_spec.rb"]
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.0}
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 = 2
28
+ s.specification_version = 3
22
29
 
23
30
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
24
- s.add_development_dependency(%q<bacon>, [">= 0"])
25
- s.add_development_dependency(%q<bones>, [">= 2.1.1"])
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<bacon>, [">= 0"])
28
- s.add_dependency(%q<bones>, [">= 2.1.1"])
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<bacon>, [">= 0"])
32
- s.add_dependency(%q<bones>, [">= 2.1.1"])
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
-
@@ -2,7 +2,7 @@
2
2
  module EMRedis
3
3
 
4
4
  # :stopdoc:
5
- VERSION = '0.1.1'
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
- end # module EMRedis
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
- require File.join(EMRedis.libpath, "em-redis/redis_protocol")
42
+ Dir.glob(search_me).sort.each {|rb| require rb}
43
+ end
44
+
45
+ end # module EMRedis
35
46
 
36
- # EOF
47
+ EMRedis.require_all_libs_relative_to(__FILE__)
@@ -10,322 +10,252 @@ module EventMachine
10
10
  # constants
11
11
  #########################
12
12
 
13
- unless defined? C_ERR
14
- C_ERR = "-".freeze
15
- C_OK = 'OK'.freeze
16
- C_SINGLE = '+'.freeze
17
- C_BULK = '$'.freeze
18
- C_MULTI = '*'.freeze
19
- C_INT = ':'.freeze
20
- C_DELIM = "\r\n".freeze
21
- end
22
-
23
- #QUIT, AUTH (NOT IMPLEMENTED)
24
- def quit(&blk)
25
- inline_command "QUIT", &blk
26
- end
27
-
28
- #GET,SET,MGET,SETNX,INCR,INCRBY,DECR,DECRBY,EXISTS,DEL,TYPE
29
- def type(key, &blk)
30
- inline_command "TYPE", key, &blk
31
- end
32
-
33
- def del(key, &blk) #delete
34
- inline_command "DEL", key, &blk
35
- end
36
- alias_method :delete, :del
37
-
38
- def exists(key, &blk) #exists?
39
- inline_command "EXISTS", key, &blk
40
- end
41
- alias_method :exists?, :exists
42
-
43
- def decrby(key, value, &blk) #decrement_by
44
- inline_command "DECRBY", key, value, &blk
45
- end
46
- alias_method :decrement_by, :decrby
47
-
48
- def decr(key, &blk) #decrement
49
- inline_command "DECR", key, &blk
50
- end
51
- alias_method :decrement, :decr
52
-
53
- def incrby(key, value, &blk)
54
- inline_command "INCRBY", key, value, &blk
55
- end
56
- alias_method :increment_by, :incrby
57
-
58
- def incr(key, &blk) #increment
59
- inline_command "INCR", key, &blk
60
- end
61
- alias_method :increment, :incr
62
-
63
- def setnx(key, value, &blk) #set_if_nil
64
- multiline_command "SETNX", key, value, &blk
65
- end
66
- alias_method :set_if_nil, :setnx
67
-
68
- def mget(*keys, &blk) #multi_get
69
- inline_command "MGET", *keys, &blk
70
- end
71
- alias_method :multi_get, :mget
72
-
73
- def set(key, value, &blk)
74
- multiline_command "SET", key, value, &blk
75
- end
76
-
77
- def get(key, &blk)
78
- inline_command "GET", key, &blk
79
- end
80
-
81
- #KEYS,RANDOMKEY,RENAME,RENAMENX,DBSIZE,EXPIRE
82
- def keys(key_search_string, &blk)
83
- wrapper = lambda {|v| blk.call(v.split)} # spec suggests splitting response on whitespace
84
- inline_command "KEYS", key_search_string, &wrapper
85
- end
86
-
87
- def randomkey(&blk) #random_key, random
88
- inline_command "RANDOMKEY", &blk
89
- end
90
- alias_method :random_key, :randomkey
91
- alias_method :random, :randomkey
92
-
93
- def rename(old_name, new_name, &blk)
94
- inline_command "RENAME", old_name, new_name, &blk
95
- end
96
-
97
- def renamenx(old_name, new_name, &blk) #rename_if_nil
98
- inline_command "RENAMENX", old_name, new_name, &blk
99
- end
100
- alias_method :rename_if_nil, :renamenx
101
-
102
- def dbsize(&blk)
103
- inline_command "DBSIZE", &blk
104
- end
105
-
106
- def expire(key, time, &blk)
107
- inline_command "EXPIRE", key, time, &blk
108
- end
109
-
110
- #RPUSH,LPUSH,LLEN,LRANGE,LTRIM,LINDEX,LSET,LREM,LPOP,RPOP
111
- def rpop(key, &blk) # tail_pop, pop
112
- inline_command "RPOP", key, &blk
113
- end
114
- alias_method :tail_pop, :rpop
115
- alias_method :pop, :rpop
116
-
117
- def lpop(key, &blk) #head_pop, shift
118
- inline_command "LPOP", key, &blk
119
- end
120
- alias_method :head_pop, :lpop
121
- alias_method :shift, :lpop
122
-
123
-
124
- def lrem(key, value, count=0, &blk) #list_remove
125
- multiline_command "LREM", key, count, value, &blk
126
- end
127
- alias_method :list_remove, :lrem
128
-
129
- def lset(key, index, value, &blk) #list_set
130
- multiline_command "LSET", key, index, value, &blk
131
- end
132
- alias_method :list_set, :lset
133
-
134
- def lindex(key, index, &blk) #list_index, index
135
- inline_command "LINDEX", key, index, &blk
136
- end
137
- alias_method :list_index, :lindex
138
- alias_method :index, :lindex
139
-
140
- def ltrim(key, start, ending, &blk) # list_trim, trim
141
- inline_command "LTRIM", key, start, ending, &blk
142
- end
143
- alias_method :list_trim, :ltrim
144
- alias_method :trim, :ltrim
145
-
146
- def lrange(key, start, range, &blk) #list_range, range
147
- inline_command "LRANGE", key, start, range, &blk
148
- end
149
- alias_method :list_range, :lrange
150
- alias_method :range, :lrange
151
-
152
- def llen(key, &blk) #list_len, len
153
- inline_command "LLEN", key, &blk
154
- end
155
- alias_method :list_len, :llen
156
- alias_method :len, :llen
157
-
158
- def lpush(key, value, &blk) #head_push, unshift
159
- multiline_command "LPUSH", key, value, &blk
160
- end
161
- alias_method :head_push, :lpush
162
- alias_method :unshift, :lpush
163
-
164
- def rpush(key, value, &blk) #tail_push, push
165
- multiline_command "RPUSH", key, value, &blk
166
- end
167
- alias_method :tail_push, :rpush
168
- alias_method :push, :rpush
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 move(key, dbindex, &blk)
230
- inline_command "MOVE", key, dbindex, &blk
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 flushdb(&blk)
234
- inline_command "FLUSHDB", &blk
182
+ def incr(key, increment = nil, &blk)
183
+ call_command(increment ? ["incrby",key,increment] : ["incr",key], &blk)
235
184
  end
236
185
 
237
- def flushall(&blk)
238
- inline_command "FLUSHALL", &blk
186
+ def decr(key, decrement = nil, &blk)
187
+ call_command(decrement ? ["decrby",key,decrement] : ["decr",key], &blk)
239
188
  end
240
189
 
241
- #SORT
242
- def sort(key, by_pattern=nil, start=nil, ending=nil, get_pattern=nil, desc=false, alpha=false, &blk)
243
- command = "SORT #{key}"
244
- command += " BY #{by_pattern}" if by_pattern
245
- command += " LIMIT #{start} #{ending}" if (start && ending)
246
- command += " GET #{get_pattern}" if get_pattern
247
- command += " DESC" if desc
248
- command += " ALPHA" if alpha
249
- inline_command command, &blk
250
- end
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
- def lastsave(&blk)
258
- inline_command "LASTSAVE", &blk
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 bgsave(&blk) #background_save, async_save
262
- inline_command "BGSAVE", &blk
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 save(&blk)
268
- inline_command "SAVE", &blk
213
+ def on_error(&blk)
214
+ @err_cb = blk
269
215
  end
270
216
 
271
- #INFO,MONITOR
272
- def info(&blk)
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 on_error(&blk)
286
- @err_cb = blk
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
- # Generic request methods
292
- #########################
293
-
294
- def inline_command(*args, &blk)
295
- callback {
296
- command = args.shift
297
- blk ||= lambda { } # all cmds must at least have a no-op callback
298
- @redis_callbacks << blk
299
- if args.size > 0
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 += C_DELIM
304
- puts "*** sending: #{command}" if $debug
305
- send_data command
306
- }
307
- end
308
-
309
- def multiline_command(command, *args, &blk)
310
- callback {
311
- data_value = args.pop.to_s
312
- blk ||= lambda { } # all cmds must at least have a no-op callback
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 += data_value.size.to_s
320
- command += C_DELIM
321
- command += data_value
322
- command += C_DELIM
323
- puts "*** sending: #{command}" if $debug
324
- send_data command
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 host = 'localhost', port = 6379
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 host, port = 6379
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 data
299
+ def receive_data(data)
370
300
  (@buffer||='') << data
371
- while index = @buffer.index(C_DELIM)
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 line
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].chr
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
- # e.g. +OK
390
- when C_SINGLE
391
- if cb = @redis_callbacks.shift
392
- cb.call( reply_args )
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 C_BULK
398
- data_len = Integer( reply_args )
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
- if cb = @redis_callbacks.shift
404
- cb.call(@values)
405
- @values = []
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
- if cb = @redis_callbacks.shift
419
- cb.call(@values)
420
- @values = []
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
- if cb = @redis_callbacks.shift
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 C_INT
438
- if cb = @redis_callbacks.shift
439
- cb.call( Integer(reply_args) )
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 C_MULTI
373
+ when ASTERISK
443
374
  @multibulk_n = Integer(reply_args)
444
- if @multibulk_n == -1
445
- if cb = @redis_callbacks.shift
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 memcached server'
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