memcache 1.2.0 → 1.2.1
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 +12 -1
- data/README.rdoc +65 -11
- data/Rakefile +14 -0
- data/VERSION.yml +2 -2
- data/bench/benchmark.rb +126 -0
- data/ext/extconf.rb +51 -2
- data/ext/libmemcached-0.38.tar.gz +0 -0
- data/ext/native_server.c +575 -0
- data/lib/memcache/base.rb +65 -0
- data/lib/memcache/local_server.rb +7 -53
- data/lib/memcache/migration.rb +7 -6
- data/lib/memcache/null_server.rb +1 -1
- data/lib/memcache/pg_server.rb +37 -20
- data/lib/memcache/{segmented_server.rb → segmented.rb} +25 -15
- data/lib/memcache/server.rb +44 -56
- data/lib/memcache.rb +131 -83
- data/test/memcache_local_server_test.rb +1 -1
- data/test/memcache_native_server_test.rb +31 -0
- data/test/memcache_null_server_test.rb +16 -8
- data/test/memcache_segmented_native_server_test.rb +18 -0
- data/test/memcache_segmented_server_test.rb +6 -9
- data/test/memcache_server_test.rb +9 -11
- data/test/memcache_server_test_helper.rb +89 -49
- data/test/memcache_test.rb +11 -19
- data/test/test_helper.rb +34 -9
- metadata +12 -4
- data/memcache.gemspec +0 -69
@@ -1,5 +1,5 @@
|
|
1
1
|
class Memcache
|
2
|
-
class LocalServer
|
2
|
+
class LocalServer < Base
|
3
3
|
def initialize
|
4
4
|
@data = {}
|
5
5
|
@expiry = {}
|
@@ -22,10 +22,6 @@ class Memcache
|
|
22
22
|
@expiry.clear
|
23
23
|
end
|
24
24
|
|
25
|
-
def gets(keys)
|
26
|
-
get(keys, true)
|
27
|
-
end
|
28
|
-
|
29
25
|
def get(keys, cas = false)
|
30
26
|
if keys.kind_of?(Array)
|
31
27
|
hash = {}
|
@@ -36,7 +32,7 @@ class Memcache
|
|
36
32
|
end
|
37
33
|
hash
|
38
34
|
else
|
39
|
-
key = keys
|
35
|
+
key = cache_key(keys)
|
40
36
|
if @expiry[key] and Time.now > @expiry[key]
|
41
37
|
@data[key] = nil
|
42
38
|
@expiry[key] = nil
|
@@ -45,63 +41,21 @@ class Memcache
|
|
45
41
|
end
|
46
42
|
end
|
47
43
|
|
48
|
-
def incr(key, amount = 1)
|
49
|
-
key = key.to_s
|
50
|
-
value = get(key)
|
51
|
-
return unless value
|
52
|
-
return unless value =~ /^\d+$/
|
53
|
-
|
54
|
-
value = value.to_i + amount
|
55
|
-
value = 0 if value < 0
|
56
|
-
@data[key] = value.to_s
|
57
|
-
value
|
58
|
-
end
|
59
|
-
|
60
|
-
def decr(key, amount = 1)
|
61
|
-
incr(key, -amount)
|
62
|
-
end
|
63
|
-
|
64
|
-
def delete(key)
|
65
|
-
@data.delete(key.to_s)
|
66
|
-
end
|
67
|
-
|
68
44
|
def set(key, value, expiry = 0, flags = 0)
|
69
|
-
key = key
|
45
|
+
key = cache_key(key)
|
70
46
|
@data[key] = value.to_s
|
47
|
+
expiry = Time.at(expiry) if expiry > 60*60*24*30
|
71
48
|
if expiry.kind_of?(Time)
|
72
49
|
@expiry[key] = expiry
|
73
|
-
else
|
50
|
+
else
|
74
51
|
expiry = expiry.to_i
|
75
52
|
@expiry[key] = expiry == 0 ? nil : Time.now + expiry
|
76
53
|
end
|
77
54
|
value
|
78
55
|
end
|
79
56
|
|
80
|
-
def
|
81
|
-
|
82
|
-
set(key, value, expiry, flags)
|
83
|
-
end
|
84
|
-
|
85
|
-
def add(key, value, expiry = 0, flags = 0)
|
86
|
-
return nil if get(key)
|
87
|
-
set(key, value, expiry)
|
88
|
-
end
|
89
|
-
|
90
|
-
def replace(key, value, expiry = 0, flags = 0)
|
91
|
-
return nil if get(key).nil?
|
92
|
-
set(key, value, expiry)
|
93
|
-
end
|
94
|
-
|
95
|
-
def append(key, value)
|
96
|
-
existing = get(key)
|
97
|
-
return nil if existing.nil?
|
98
|
-
set(key, existing + value)
|
99
|
-
end
|
100
|
-
|
101
|
-
def prepend(key, value)
|
102
|
-
existing = get(key)
|
103
|
-
return nil if existing.nil?
|
104
|
-
set(key, value + existing)
|
57
|
+
def delete(key)
|
58
|
+
@data.delete(cache_key(key)) && true
|
105
59
|
end
|
106
60
|
end
|
107
61
|
end
|
data/lib/memcache/migration.rb
CHANGED
@@ -3,19 +3,20 @@ class Memcache
|
|
3
3
|
class << self
|
4
4
|
attr_accessor :table
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
def self.up
|
8
8
|
create_table table, :id => false do |t|
|
9
|
-
t.string :
|
10
|
-
t.
|
9
|
+
t.string :prefix, :null => false
|
10
|
+
t.string :key, :null => false
|
11
|
+
t.text :value, :null => false
|
11
12
|
t.timestamp :expires_at
|
12
13
|
t.timestamp :updated_at
|
13
14
|
end
|
14
|
-
|
15
|
-
add_index table, [:key], :unique => true
|
15
|
+
|
16
|
+
add_index table, [:prefix, :key], :unique => true
|
16
17
|
add_index table, [:expires_at]
|
17
18
|
end
|
18
|
-
|
19
|
+
|
19
20
|
def self.down
|
20
21
|
drop_table table
|
21
22
|
end
|
data/lib/memcache/null_server.rb
CHANGED
data/lib/memcache/pg_server.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require 'memcache/migration'
|
3
3
|
|
4
|
+
class PGconn
|
5
|
+
def self.quote_ident(name)
|
6
|
+
%("#{name}")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
4
10
|
class Memcache
|
5
|
-
class PGServer
|
11
|
+
class PGServer < Base
|
6
12
|
attr_reader :db, :table
|
7
13
|
|
8
14
|
def initialize(opts)
|
@@ -23,11 +29,12 @@ class Memcache
|
|
23
29
|
|
24
30
|
def get(keys)
|
25
31
|
return get([keys])[keys.to_s] unless keys.kind_of?(Array)
|
32
|
+
return {} if keys.empty?
|
26
33
|
|
27
34
|
keys = keys.collect {|key| quote(key.to_s)}.join(',')
|
28
35
|
sql = %{
|
29
36
|
SELECT key, value FROM #{table}
|
30
|
-
WHERE key IN (#{keys}) AND #{expiry_clause}
|
37
|
+
WHERE key IN (#{keys}) AND #{prefix_clause} AND #{expiry_clause}
|
31
38
|
}
|
32
39
|
results = {}
|
33
40
|
db.query(sql).each do |key, value|
|
@@ -39,14 +46,14 @@ class Memcache
|
|
39
46
|
def incr(key, amount = 1)
|
40
47
|
transaction do
|
41
48
|
value = get(key)
|
42
|
-
return unless value
|
49
|
+
return unless value
|
43
50
|
return unless value =~ /^\d+$/
|
44
|
-
|
51
|
+
|
45
52
|
value = value.to_i + amount
|
46
53
|
value = 0 if value < 0
|
47
54
|
db.exec %{
|
48
55
|
UPDATE #{table} SET value = #{quote(value)}, updated_at = NOW()
|
49
|
-
WHERE key = #{quote(key)}
|
56
|
+
WHERE key = #{quote(key)} AND #{prefix_clause}
|
50
57
|
}
|
51
58
|
value
|
52
59
|
end
|
@@ -59,8 +66,9 @@ class Memcache
|
|
59
66
|
def delete(key)
|
60
67
|
result = db.exec %{
|
61
68
|
DELETE FROM #{table}
|
62
|
-
WHERE key = #{quote(key)}
|
69
|
+
WHERE key = #{quote(key)} AND #{prefix_clause}
|
63
70
|
}
|
71
|
+
result.cmdtuples == 1
|
64
72
|
end
|
65
73
|
|
66
74
|
def set(key, value, expiry = 0)
|
@@ -79,38 +87,38 @@ class Memcache
|
|
79
87
|
nil
|
80
88
|
end
|
81
89
|
|
82
|
-
def replace(key, value, expiry = 0)
|
90
|
+
def replace(key, value, expiry = 0)
|
83
91
|
delete_expired(key)
|
84
92
|
result = update(key, value, expiry)
|
85
93
|
result.cmdtuples == 1 ? value : nil
|
86
94
|
end
|
87
95
|
|
88
|
-
def append(key, value)
|
96
|
+
def append(key, value)
|
89
97
|
delete_expired(key)
|
90
98
|
result = db.exec %{
|
91
99
|
UPDATE #{table}
|
92
100
|
SET value = value || #{quote(value)}, updated_at = NOW()
|
93
|
-
WHERE key = #{quote(key)}
|
101
|
+
WHERE key = #{quote(key)} AND #{prefix_clause}
|
94
102
|
}
|
95
103
|
result.cmdtuples == 1
|
96
104
|
end
|
97
105
|
|
98
|
-
def prepend(key, value)
|
106
|
+
def prepend(key, value)
|
99
107
|
delete_expired(key)
|
100
108
|
result = db.exec %{
|
101
109
|
UPDATE #{table}
|
102
110
|
SET value = #{quote(value)} || value, updated_at = NOW()
|
103
|
-
WHERE key = #{quote(key)}
|
111
|
+
WHERE key = #{quote(key)} AND #{prefix_clause}
|
104
112
|
}
|
105
113
|
result.cmdtuples == 1
|
106
114
|
end
|
107
115
|
|
108
116
|
private
|
109
|
-
|
117
|
+
|
110
118
|
def insert(key, value, expiry = 0)
|
111
119
|
db.exec %{
|
112
|
-
INSERT INTO #{table} (key, value, updated_at, expires_at)
|
113
|
-
VALUES (#{quote(key)}, #{quote(value)}, NOW(), #{expiry_sql(expiry)})
|
120
|
+
INSERT INTO #{table} (prefix, key, value, updated_at, expires_at)
|
121
|
+
VALUES (#{quoted_prefix}, #{quote(key)}, #{quote(value)}, NOW(), #{expiry_sql(expiry)})
|
114
122
|
}
|
115
123
|
end
|
116
124
|
|
@@ -118,14 +126,14 @@ class Memcache
|
|
118
126
|
db.exec %{
|
119
127
|
UPDATE #{table}
|
120
128
|
SET value = #{quote(value)}, updated_at = NOW(), expires_at = #{expiry_sql(expiry)}
|
121
|
-
WHERE key = #{quote(key)}
|
129
|
+
WHERE key = #{quote(key)} AND #{prefix_clause}
|
122
130
|
}
|
123
131
|
end
|
124
132
|
|
125
133
|
def transaction
|
126
134
|
return yield if @in_transaction
|
127
|
-
|
128
|
-
begin
|
135
|
+
|
136
|
+
begin
|
129
137
|
@in_transaction = true
|
130
138
|
db.exec('BEGIN')
|
131
139
|
value = yield
|
@@ -138,26 +146,35 @@ class Memcache
|
|
138
146
|
@in_transaction = false
|
139
147
|
end
|
140
148
|
end
|
141
|
-
|
149
|
+
|
142
150
|
def quote(string)
|
143
151
|
string.to_s.gsub(/'/,"\'")
|
144
152
|
"'#{string}'"
|
145
153
|
end
|
146
154
|
|
147
155
|
def delete_expired(key)
|
148
|
-
db.exec "DELETE FROM #{table} WHERE key = #{quote(key)} AND NOT (#{expiry_clause})"
|
156
|
+
db.exec "DELETE FROM #{table} WHERE key = #{quote(key)} AND #{prefix_clause} AND NOT (#{expiry_clause})"
|
149
157
|
end
|
150
|
-
|
158
|
+
|
151
159
|
def expiry_clause
|
152
160
|
"expires_at IS NULL OR expires_at > NOW()"
|
153
161
|
end
|
154
162
|
|
155
163
|
def expiry_sql(expiry)
|
164
|
+
expiry = Time.at(expiry) if expiry > 60*60*24*30
|
156
165
|
if expiry.kind_of?(Time)
|
157
166
|
quote(expiry.to_s(:db))
|
158
167
|
else
|
159
168
|
expiry == 0 ? 'NULL' : "NOW() + interval '#{expiry} seconds'"
|
160
169
|
end
|
161
170
|
end
|
171
|
+
|
172
|
+
def quoted_prefix
|
173
|
+
quote(prefix || '')
|
174
|
+
end
|
175
|
+
|
176
|
+
def prefix_clause
|
177
|
+
"prefix = #{quoted_prefix}"
|
178
|
+
end
|
162
179
|
end
|
163
180
|
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
require 'digest/sha1'
|
2
2
|
|
3
3
|
class Memcache
|
4
|
-
|
4
|
+
module Segmented
|
5
5
|
MAX_SIZE = 1000000 # bytes
|
6
6
|
PARTIAL_VALUE = 0x40000000
|
7
|
-
|
7
|
+
|
8
8
|
def get(keys, cas = nil)
|
9
9
|
return get([keys], cas)[keys.to_s] unless keys.kind_of?(Array)
|
10
10
|
return {} if keys.empty?
|
11
11
|
|
12
|
-
results = super
|
12
|
+
results = super(keys, cas)
|
13
13
|
keys = {}
|
14
14
|
keys_to_fetch = []
|
15
15
|
results.each do |key, value|
|
@@ -19,14 +19,14 @@ class Memcache
|
|
19
19
|
num.to_i.times do |i|
|
20
20
|
hash_key = "#{hash}:#{i}"
|
21
21
|
keys_to_fetch << hash_key
|
22
|
-
keys[key] << hash_key
|
22
|
+
keys[key] << hash_key
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
parts = super(keys_to_fetch)
|
27
27
|
keys.each do |key, hashes|
|
28
28
|
value = ''
|
29
|
-
hashes.each do |hash_key|
|
29
|
+
hashes.each do |hash_key|
|
30
30
|
if part = parts[hash_key]
|
31
31
|
value << part
|
32
32
|
else
|
@@ -45,23 +45,23 @@ class Memcache
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def set(key, value, expiry = 0, flags = 0)
|
48
|
-
|
49
|
-
super(key,
|
48
|
+
hash, flags = store_segments(key, value, expiry, flags)
|
49
|
+
super(key, hash, expiry, flags) && value
|
50
50
|
end
|
51
51
|
|
52
52
|
def cas(key, value, cas, expiry = 0, flags = 0)
|
53
|
-
|
54
|
-
super(key,
|
53
|
+
hash, flags = store_segments(key, value, expiry, flags)
|
54
|
+
super(key, hash, cas, expiry, flags) && value
|
55
55
|
end
|
56
56
|
|
57
57
|
def add(key, value, expiry = 0, flags = 0)
|
58
|
-
|
59
|
-
super(key,
|
58
|
+
hash, flags = store_segments(key, value, expiry, flags)
|
59
|
+
super(key, hash, expiry, flags) && value
|
60
60
|
end
|
61
61
|
|
62
62
|
def replace(key, value, expiry = 0, flags = 0)
|
63
|
-
|
64
|
-
super(key,
|
63
|
+
hash, flags = store_segments(key, value, expiry, flags)
|
64
|
+
super(key, hash, expiry, flags) && value
|
65
65
|
end
|
66
66
|
|
67
67
|
private
|
@@ -81,7 +81,7 @@ class Memcache
|
|
81
81
|
master_key = "#{hash}:#{parts.size}"
|
82
82
|
[master_key, parts]
|
83
83
|
end
|
84
|
-
|
84
|
+
|
85
85
|
def store_segments(key, value, expiry = 0, flags = 0)
|
86
86
|
if value and value.size > MAX_SIZE
|
87
87
|
master_key, parts = segment(key, value)
|
@@ -95,4 +95,14 @@ class Memcache
|
|
95
95
|
end
|
96
96
|
end
|
97
97
|
end
|
98
|
+
|
99
|
+
class SegmentedServer < Server
|
100
|
+
include Memcache::Segmented
|
101
|
+
end
|
102
|
+
|
103
|
+
if defined?(NativeServer)
|
104
|
+
class SegmentedNativeServer < NativeServer
|
105
|
+
include Memcache::Segmented
|
106
|
+
end
|
107
|
+
end
|
98
108
|
end
|
data/lib/memcache/server.rb
CHANGED
@@ -3,7 +3,7 @@ require 'thread'
|
|
3
3
|
require 'timeout'
|
4
4
|
|
5
5
|
class Memcache
|
6
|
-
class Server
|
6
|
+
class Server < Base
|
7
7
|
CONNECT_TIMEOUT = 1.0
|
8
8
|
READ_RETRY_DELAY = 5.0
|
9
9
|
DEFAULT_PORT = 11211
|
@@ -13,13 +13,12 @@ class Memcache
|
|
13
13
|
def initialize(opts)
|
14
14
|
@host = opts[:host]
|
15
15
|
@port = opts[:port] || DEFAULT_PORT
|
16
|
-
@readonly = opts[:readonly]
|
17
16
|
@strict_reads = opts[:strict_reads]
|
18
17
|
@status = 'NOT CONNECTED'
|
19
18
|
end
|
20
19
|
|
21
20
|
def clone
|
22
|
-
self.class.new(:host => host, :port => port, :
|
21
|
+
self.class.new(:host => host, :port => port, :strict_reads => strict_reads?)
|
23
22
|
end
|
24
23
|
|
25
24
|
def inspect
|
@@ -34,10 +33,6 @@ class Memcache
|
|
34
33
|
@retry_at.nil? or @retry_at < Time.now
|
35
34
|
end
|
36
35
|
|
37
|
-
def readonly?
|
38
|
-
@readonly
|
39
|
-
end
|
40
|
-
|
41
36
|
def strict_reads?
|
42
37
|
@strict_reads
|
43
38
|
end
|
@@ -46,7 +41,7 @@ class Memcache
|
|
46
41
|
# Close the socket. If there is an error, mark the server dead.
|
47
42
|
@socket.close if @socket and not @socket.closed?
|
48
43
|
@socket = nil
|
49
|
-
|
44
|
+
|
50
45
|
if error
|
51
46
|
@retry_at = Time.now + READ_RETRY_DELAY
|
52
47
|
@status = "DEAD: %s: %s, will retry at %s" % [error.class, error.message, @retry_at]
|
@@ -71,22 +66,15 @@ class Memcache
|
|
71
66
|
end
|
72
67
|
stats
|
73
68
|
end
|
74
|
-
|
69
|
+
|
75
70
|
def count
|
76
71
|
stats['curr_items']
|
77
72
|
end
|
78
73
|
|
79
74
|
def flush_all(delay = nil)
|
80
|
-
check_writable!
|
81
75
|
write_command("flush_all #{delay}")
|
82
76
|
end
|
83
77
|
|
84
|
-
alias clear flush_all
|
85
|
-
|
86
|
-
def gets(keys)
|
87
|
-
get(keys, true)
|
88
|
-
end
|
89
|
-
|
90
78
|
def get(keys, cas = nil)
|
91
79
|
return get([keys], cas)[keys.to_s] unless keys.kind_of?(Array)
|
92
80
|
return {} if keys.empty?
|
@@ -94,99 +82,99 @@ class Memcache
|
|
94
82
|
method = cas ? 'gets' : 'get'
|
95
83
|
|
96
84
|
results = {}
|
85
|
+
keys = keys.collect {|key| cache_key(key)}
|
86
|
+
|
97
87
|
read_command("#{method} #{keys.join(' ')}") do |response|
|
98
88
|
if cas
|
99
89
|
key, flags, length, cas = match_response!(response, /^VALUE ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+)/)
|
100
90
|
else
|
101
91
|
key, flags, length = match_response!(response, /^VALUE ([^\s]+) ([^\s]+) ([^\s]+)/)
|
102
92
|
end
|
103
|
-
|
93
|
+
|
104
94
|
value = socket.read(length.to_i)
|
105
95
|
match_response!(socket.read(2), "\r\n")
|
106
96
|
|
107
97
|
value.memcache_flags = flags.to_i
|
108
98
|
value.memcache_cas = cas
|
99
|
+
|
100
|
+
key = input_key(key)
|
109
101
|
results[key] = value
|
110
102
|
end
|
111
103
|
results
|
112
104
|
end
|
113
105
|
|
114
106
|
def incr(key, amount = 1)
|
115
|
-
check_writable!
|
116
107
|
raise Error, "incr requires unsigned value" if amount < 0
|
117
|
-
response = write_command("incr #{key} #{amount}")
|
118
|
-
response == "NOT_FOUND\r\n" ? nil : response.to_i
|
108
|
+
response = write_command("incr #{cache_key(key)} #{amount}")
|
109
|
+
response == "NOT_FOUND\r\n" ? nil : response.slice(0..-3).to_i
|
119
110
|
end
|
120
111
|
|
121
112
|
def decr(key, amount = 1)
|
122
|
-
check_writable!
|
123
113
|
raise Error, "decr requires unsigned value" if amount < 0
|
124
|
-
response = write_command("decr #{key} #{amount}")
|
125
|
-
response == "NOT_FOUND\r\n" ? nil : response.to_i
|
114
|
+
response = write_command("decr #{cache_key(key)} #{amount}")
|
115
|
+
response == "NOT_FOUND\r\n" ? nil : response.slice(0..-3).to_i
|
126
116
|
end
|
127
117
|
|
128
118
|
def delete(key)
|
129
|
-
|
130
|
-
write_command("delete #{key}") == "DELETED\r\n" ? true : nil
|
119
|
+
write_command("delete #{cache_key(key)}") == "DELETED\r\n" ? true : nil
|
131
120
|
end
|
132
121
|
|
133
122
|
def set(key, value, expiry = 0, flags = 0)
|
134
123
|
return delete(key) if value.nil?
|
135
|
-
|
136
|
-
write_command("set #{key} #{flags.to_i} #{expiry.to_i} #{value.to_s.size}", value)
|
124
|
+
write_command("set #{cache_key(key)} #{flags.to_i} #{expiry.to_i} #{value.to_s.size}", value)
|
137
125
|
value
|
138
126
|
end
|
139
127
|
|
140
128
|
def cas(key, value, cas, expiry = 0, flags = 0)
|
141
|
-
|
142
|
-
response = write_command("cas #{key} #{flags.to_i} #{expiry.to_i} #{value.to_s.size} #{cas.to_i}", value)
|
129
|
+
response = write_command("cas #{cache_key(key)} #{flags.to_i} #{expiry.to_i} #{value.to_s.size} #{cas.to_i}", value)
|
143
130
|
response == "STORED\r\n" ? value : nil
|
144
131
|
end
|
145
132
|
|
146
133
|
def add(key, value, expiry = 0, flags = 0)
|
147
|
-
|
148
|
-
response = write_command("add #{key} #{flags.to_i} #{expiry.to_i} #{value.to_s.size}", value)
|
134
|
+
response = write_command("add #{cache_key(key)} #{flags.to_i} #{expiry.to_i} #{value.to_s.size}", value)
|
149
135
|
response == "STORED\r\n" ? value : nil
|
150
136
|
end
|
151
137
|
|
152
138
|
def replace(key, value, expiry = 0, flags = 0)
|
153
|
-
|
154
|
-
response = write_command("replace #{key} #{flags.to_i} #{expiry.to_i} #{value.to_s.size}", value)
|
139
|
+
response = write_command("replace #{cache_key(key)} #{flags.to_i} #{expiry.to_i} #{value.to_s.size}", value)
|
155
140
|
response == "STORED\r\n" ? value : nil
|
156
141
|
end
|
157
142
|
|
158
143
|
def append(key, value)
|
159
|
-
|
160
|
-
response = write_command("append #{key} 0 0 #{value.to_s.size}", value)
|
144
|
+
response = write_command("append #{cache_key(key)} 0 0 #{value.to_s.size}", value)
|
161
145
|
response == "STORED\r\n"
|
162
146
|
end
|
163
147
|
|
164
148
|
def prepend(key, value)
|
165
|
-
|
166
|
-
response = write_command("prepend #{key} 0 0 #{value.to_s.size}", value)
|
149
|
+
response = write_command("prepend #{cache_key(key)} 0 0 #{value.to_s.size}", value)
|
167
150
|
response == "STORED\r\n"
|
168
151
|
end
|
169
152
|
|
170
|
-
|
171
|
-
class ConnectionError < Error
|
172
|
-
def initialize(e)
|
173
|
-
if e.kind_of?(String)
|
174
|
-
super
|
175
|
-
else
|
176
|
-
super("(#{e.class}) #{e.message}")
|
177
|
-
set_backtrace(e.backtrace)
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
class ServerError < Error; end
|
182
|
-
class ClientError < Error; end
|
153
|
+
protected
|
183
154
|
|
184
|
-
|
155
|
+
ESCAPE = {
|
156
|
+
" " => '\s',
|
157
|
+
"\t" => '\t',
|
158
|
+
"\n" => '\n',
|
159
|
+
"\v" => '\v',
|
160
|
+
"\f" => '\f',
|
161
|
+
"\\" => '\\\\',
|
162
|
+
}
|
163
|
+
UNESCAPE = ESCAPE.invert
|
164
|
+
|
165
|
+
def input_key(key)
|
166
|
+
key = key[prefix.size..-1] if prefix # Remove prefix from key.
|
167
|
+
key = key.gsub(/\\./) {|c| UNESCAPE[c]}
|
168
|
+
key
|
169
|
+
end
|
185
170
|
|
186
|
-
def
|
187
|
-
|
171
|
+
def cache_key(key)
|
172
|
+
key = key.gsub(/[\s\\]/) {|c| ESCAPE[c]}
|
173
|
+
super(key)
|
188
174
|
end
|
189
175
|
|
176
|
+
private
|
177
|
+
|
190
178
|
def match_response!(response, regexp)
|
191
179
|
# Make sure that the response matches the protocol.
|
192
180
|
unexpected_eof! if response.nil?
|
@@ -200,12 +188,12 @@ class Memcache
|
|
200
188
|
command = command.join("\r\n")
|
201
189
|
socket.write("#{command}\r\n")
|
202
190
|
response = socket.gets
|
203
|
-
|
191
|
+
|
204
192
|
unexpected_eof! if response.nil?
|
205
193
|
if response =~ /^(ERROR|CLIENT_ERROR|SERVER_ERROR) (.*)\r\n/
|
206
194
|
raise ($1 == 'SERVER_ERROR' ? ServerError : ClientError), $2
|
207
195
|
end
|
208
|
-
|
196
|
+
|
209
197
|
block_given? ? yield(response) : response
|
210
198
|
rescue Exception => e
|
211
199
|
close(e) # Mark dead.
|
@@ -251,7 +239,7 @@ class Memcache
|
|
251
239
|
if Socket.constants.include? 'TCP_NODELAY'
|
252
240
|
@socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
253
241
|
end
|
254
|
-
|
242
|
+
|
255
243
|
@retry_at = nil
|
256
244
|
@status = 'CONNECTED'
|
257
245
|
end
|