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.
@@ -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.to_s
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.to_s
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 cas(key, value, cas, expiry = 0, flags = 0)
81
- # No cas implementation yet, just do a set for now.
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
@@ -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 :key
10
- t.text :value
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
@@ -1,5 +1,5 @@
1
1
  class Memcache
2
- class NullServer
2
+ class NullServer < Base
3
3
  def name
4
4
  "null"
5
5
  end
@@ -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
- class SegmentedServer < Server
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
- value, flags = store_segments(key, value, expiry, flags)
49
- super(key, value, expiry, flags) && value
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
- value, flags = store_segments(key, value, expiry, flags)
54
- super(key, value, cas, expiry, flags)
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
- value, flags = store_segments(key, value, expiry, flags)
59
- super(key, value, expiry, flags)
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
- value, flags = store_segments(key, value, expiry, flags)
64
- super(key, value, expiry, flags)
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
@@ -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, :readonly => readonly?, :strict_reads => strict_reads?)
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
- check_writable!
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
- check_writable!
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
- check_writable!
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
- check_writable!
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
- check_writable!
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
- check_writable!
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
- check_writable!
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
- class Error < StandardError; end
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
- private
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 check_writable!
187
- raise Error, "Update of readonly cache" if readonly?
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