couchbase 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,71 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'thread'
19
+
20
+ module Couchbase
21
+ class Latch
22
+ attr_reader :state, :target
23
+
24
+ # Takes initial pair of possible states and set latch in the first.
25
+ #
26
+ # @example Read an attribute.
27
+ # Latch.new(false, true)
28
+ # Latch.new(:closed, :opened)
29
+ #
30
+ # @param [ Object ] from Initial state
31
+ #
32
+ # @param [ Object ] to Target state
33
+ def initialize(from, to)
34
+ @state = from
35
+ @target = to
36
+ @lock = Mutex.new
37
+ @condition = ConditionVariable.new
38
+ end
39
+
40
+
41
+ # Turn latch to target state.
42
+ #
43
+ # @example
44
+ # l = Latch.new(:opened, :closed)
45
+ # l.state #=> :opened
46
+ # l.toggle #=> :closed
47
+ #
48
+ # @return [ Object ] Target state
49
+ def toggle
50
+ @lock.synchronize do
51
+ @state = @target
52
+ @condition.broadcast
53
+ end
54
+ @state
55
+ end
56
+
57
+ # Perform blocking wait operation until state will be toggled.
58
+ #
59
+ # @example
60
+ # l = Latch.new(:opened, :closed)
61
+ # l.wait #=> :closed
62
+ #
63
+ # @return [ Object ] Target state
64
+ def wait
65
+ @lock.synchronize do
66
+ @condition.wait(@lock) while @state != @target
67
+ end
68
+ @state
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,372 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'memcached'
19
+
20
+ module Couchbase
21
+
22
+ # This module is included in the Couchbase::Connection and provides
23
+ # routines for Memcached API
24
+
25
+ module Memcached
26
+
27
+ attr_reader :memcached, :default_format, :default_flags, :default_ttl
28
+
29
+ # Initializes Memcached API. It builds a server list using moxi ports.
30
+ #
31
+ # @param [Hash] options The options for module
32
+ # @option options [Symbol] :format format of the values. It can be on of
33
+ # the <tt>[:plain, :document, :marshal]</tt>. You can choose
34
+ # <tt>:plain</tt> if you no need any conversions to be applied to your
35
+ # data, but your data should be passed as String. Choose
36
+ # <tt>:document</tt> (default) format when you'll store hashes and plan
37
+ # to execute CouchDB views with map/reduce operations on them. And
38
+ # finally use <tt>:marshal</tt> format if you'd like to transparently
39
+ # serialize almost any ruby object with standard <tt>Marshal.dump</tt>
40
+ # and <tt>Marhal.load</tt> methods.
41
+ def initialize(pool_uri, options = {})
42
+ @default_format = options[:format] || :document
43
+ @default_flags = ::Memcached::FLAGS
44
+ @options = {
45
+ :binary_protocol => true,
46
+ :support_cas => true,
47
+ :default_ttl => 0
48
+ }.merge(options || {})
49
+ @options[:experimental_features] = true
50
+ if @credentials
51
+ @options[:credentials] = [@credentials[:username], @credentials[:password]]
52
+ end
53
+ super
54
+ end
55
+
56
+ # Returns effective options from Memcached instance
57
+ #
58
+ # @return [Hash]
59
+ def options
60
+ @memcached.options
61
+ end
62
+
63
+ # Return the array of server strings used to configure this instance.
64
+ #
65
+ # @return [Array]
66
+ def servers
67
+ @memcached.servers
68
+ end
69
+
70
+ # Set the prefix key.
71
+ #
72
+ # @param [String] prefix the string to prepend before each key.
73
+ def prefix_key=(prefix)
74
+ @memcached.prefix_key(prefix)
75
+ end
76
+ alias :namespace= :prefix_key=
77
+
78
+ # Return the current prefix key.
79
+ #
80
+ # @return [String]
81
+ def prefix_key
82
+ @memcached.prefix_key
83
+ end
84
+ alias :namespace :prefix_key
85
+
86
+ # Return a hash of statistics responses from the set of servers. Each
87
+ # value is an array with one entry for each server, in the same order the
88
+ # servers were defined.
89
+ #
90
+ # @param [String] key The name of the statistical item. When key is nil,
91
+ # the server will return all set of statistics information.
92
+ #
93
+ # @return [Hash]
94
+ def stats(key = nil)
95
+ @memcached.stats(key)
96
+ end
97
+
98
+ # Flushes all key/value pairs from all the servers.
99
+ def flush
100
+ @memcached.flush
101
+ end
102
+
103
+ # Gets a key's value from the server. It will use <tt>multiget</tt>
104
+ # behaviour if you pass Array of keys, which is much faster than normal
105
+ # mode.
106
+ #
107
+ # @overload get(key, options = {})
108
+ # Get single key.
109
+ #
110
+ # @param [String] key
111
+ # @param [Hash] options the options for operation
112
+ # @option options [Symbol] :format (self.default_format) format of the value
113
+ # @return [Object] the value is associated with the key
114
+ # @raise [Memcached::NotFound] if the key does not exist on the server.
115
+ #
116
+ # @overload get(*keys, options = {})
117
+ # Get multiple keys aka multiget (mget).
118
+ #
119
+ # @param [Array] keys the list of keys
120
+ # @param [Hash] options the options for operation
121
+ # @option options [Symbol] :format (self.default_format) format of the value
122
+ # @return [Hash] result map, where keys are keys which were requested
123
+ # and values are the values from the storage. Result will contain only
124
+ # existing keys and in this case no exception will be raised.
125
+ def get(*args)
126
+ options = args.last.is_a?(Hash) ? args.pop : {}
127
+ raise ArgumentError, "You must provide at least one key" if args.empty?
128
+ keys = args.length == 1 ? args.pop : args
129
+ format = options[:format] || @default_format
130
+ rv = @memcached.get(keys, format == :marshal)
131
+ if keys.is_a?(Array)
132
+ rv.keys.each do |key|
133
+ rv[key] = decode(rv[key], format)
134
+ end
135
+ rv
136
+ else
137
+ decode(rv, format)
138
+ end
139
+ end
140
+
141
+ # Shortcut to <tt>#get</tt> operation. Gets a key's value from the server
142
+ # with default options. (@see #get method for additional info)
143
+ #
144
+ # @param [Array, String] keys list of String keys or single key
145
+ #
146
+ # @raise [Memcached::NotFound] if the key does not exist on the server.
147
+ def [](keys)
148
+ decode(@memcached.get(keys, @default_format == :marshal), @default_format)
149
+ end
150
+
151
+ # Set new expiration time for existing item. The <tt>ttl</tt> parameter
152
+ # will use <tt>#default_ttl</tt> value if it is nil.
153
+ #
154
+ # @param [String] key
155
+ #
156
+ # @param [Fixnum] ttl
157
+ def touch(key, ttl = @default_ttl)
158
+ @memcached.touch(key, ttl)
159
+ end
160
+
161
+ # Set a key/value pair. Overwrites any existing value on the server.
162
+ #
163
+ # @param [String] key
164
+ #
165
+ # @param [Object] value
166
+ #
167
+ # @param [Hash] options the options for operation
168
+ # @option options [String] :ttl (self.default_ttl) the time to live of this key
169
+ # @option options [Symbol] :format (self.default_format) format of the value
170
+ # @option options [Fixnum] :flags (self.default_flags) flags for this key
171
+ def set(key, value, options = {})
172
+ ttl = options[:ttl] || @default_ttl
173
+ format = options[:format] || @default_format
174
+ flags = options[:flags] || @default_flags
175
+ @memcached.set(key, encode(value, format), ttl, format == :marshal, flags)
176
+ end
177
+
178
+ # Shortcut to <tt>#set</tt> operation. Sets key to given value using
179
+ # default options.
180
+ #
181
+ # @param [String] key
182
+ #
183
+ # @param [Object] value
184
+ def []=(key, value)
185
+ @memcached.set(key, encode(value, @default_format),
186
+ @default_ttl, @default_format == :marshal, @default_flags)
187
+ end
188
+
189
+ # Reads a key's value from the server and yields it to a block. Replaces
190
+ # the key's value with the result of the block as long as the key hasn't
191
+ # been updated in the meantime, otherwise raises
192
+ # <b>Memcached::NotStored</b>. CAS stands for "compare and swap", and
193
+ # avoids the need for manual key mutexing. Read more info here:
194
+ #
195
+ # http://docs.couchbase.org/memcached-api/memcached-api-protocol-text.html#memcached-api-protocol-text_cas
196
+ #
197
+ # @param [String] key
198
+ #
199
+ # @param [Hash] options the options for operation
200
+ # @option options [String] :ttl (self.default_ttl) the time to live of this key
201
+ # @option options [Symbol] :format (self.default_format) format of the value
202
+ # @option options [Fixnum] :flags (self.default_flags) flags for this key
203
+ #
204
+ # @yieldparam [Object] value old value.
205
+ # @yieldreturn [Object] new value.
206
+ #
207
+ # @raise [Memcached::ClientError] if CAS doesn't enabled for current
208
+ # connection. (:support_cas is true by default)
209
+ # @raise [Memcached::NotStored] if the key was updated before the the
210
+ # code in block has been completed (the CAS value has been changed).
211
+ #
212
+ # @example Implement append to JSON encoded value
213
+ #
214
+ # c.default_format #=> :json
215
+ # c.set("foo", {"bar" => 1})
216
+ # c.cas("foo") do |val|
217
+ # val["baz"] = 2
218
+ # val
219
+ # end
220
+ # c.get("foo") #=> {"bar" => 1, "baz" => 2}
221
+ #
222
+ def cas(key, options = {}, &block)
223
+ ttl = options[:ttl] || @default_ttl
224
+ format = options[:format] || @default_format
225
+ flags = options[:flags] || @default_flags
226
+ @memcached.cas(key, ttl, format == :marshal, flags) do |value|
227
+ value = decode(value, format)
228
+ encode(block.call(value), format)
229
+ end
230
+ end
231
+ alias :compare_and_swap :cas
232
+
233
+ # Add a key/value pair.
234
+ #
235
+ # @param [String] key
236
+ #
237
+ # @param [Object] value
238
+ #
239
+ # @param [Hash] options the options for operation
240
+ # @option options [String] :ttl (self.default_ttl) the time to live of this key
241
+ # @option options [Symbol] :format (self.default_format) format of the value
242
+ # @option options [Fixnum] :flags (self.default_flags) flags for this key
243
+ #
244
+ # @raise [Memcached::NotStored] if the key already exists on the server.
245
+ def add(key, value, options = {})
246
+ ttl = options[:ttl] || @default_ttl
247
+ format = options[:format] || @default_format
248
+ flags = options[:flags] || @default_flags
249
+ @memcached.add(key, encode(value, format), ttl, format == :marshal, flags)
250
+ end
251
+
252
+
253
+ # Replace a key/value pair.
254
+ #
255
+ # @param [String] key
256
+ #
257
+ # @param [Object] value
258
+ #
259
+ # @param [Hash] options the options for operation
260
+ # @option options [String] :ttl (self.default_ttl) the time to live of this key
261
+ # @option options [Symbol] :format (self.default_format) format of the value
262
+ # @option options [Fixnum] :flags (self.default_flags) flags for this key
263
+ #
264
+ # @raise [Memcached::NotFound] if the key doesn't exists on the server.
265
+ def replace(key, value, options = {})
266
+ ttl = options[:ttl] || @default_ttl
267
+ format = options[:format] || @default_format
268
+ flags = options[:flags] || @default_flags
269
+ @memcached.replace(key, encode(value, format), ttl, format == :marshal, flags)
270
+ end
271
+
272
+ # Appends a string to a key's value. Make sense for <tt>:plain</tt>
273
+ # format, because server doesn't make assumptions about value structure
274
+ # here.
275
+ #
276
+ # @param [String] key
277
+ #
278
+ # @param [Object] value
279
+ #
280
+ # @raise [Memcached::NotFound] if the key doesn't exists on the server.
281
+ def append(key, value)
282
+ @memcached.append(key, value)
283
+ end
284
+
285
+
286
+ # Prepends a string to a key's value. Make sense for <tt>:plain</tt>
287
+ # format, because server doesn't make assumptions about value structure
288
+ # here.
289
+ #
290
+ # @param [String] key
291
+ #
292
+ # @param [Object] value
293
+ #
294
+ # @raise [Memcached::NotFound] if the key doesn't exists on the server.
295
+ def prepend(key, value)
296
+ @memcached.prepend(key, value)
297
+ end
298
+
299
+ # Deletes a key/value pair from the server.
300
+ #
301
+ # @param [String] key
302
+ #
303
+ # @raise [Memcached::NotFound] if the key doesn't exists on the server.
304
+ def delete(key)
305
+ @memcached.delete(key)
306
+ end
307
+
308
+ # Increment a key's value. The key must be initialized to a plain integer
309
+ # first via <tt>#set</tt>, <tt>#add</tt>, or <tt>#replace</tt> with
310
+ # <tt>:format</tt> set to <tt>:plain</tt>.
311
+ #
312
+ # @param [String] key
313
+ #
314
+ # @param [Fixnum] offset the value to add
315
+ def increment(key, offset = 1)
316
+ @memcached.increment(key, offset)
317
+ end
318
+ alias :incr :increment
319
+
320
+ # Decrement a key's value. The key must be initialized to a plain integer
321
+ # first via <tt>#set</tt>, <tt>#add</tt>, or <tt>#replace</tt> with
322
+ # <tt>:format</tt> set to <tt>:plain</tt>.
323
+ #
324
+ # @param [String] key
325
+ #
326
+ # @param [Fixnum] offset the value to substract
327
+ def decrement(key, offset = 1)
328
+ @memcached.decrement(key, offset)
329
+ end
330
+ alias :decr :decrement
331
+
332
+ # Safely copy this instance.
333
+ #
334
+ # <tt>clone</tt> is useful for threading, since each thread must have its own unshared object.
335
+ def clone
336
+ double = super
337
+ double.instance_variable_set("@memcached", @memcached.clone)
338
+ double
339
+ end
340
+ alias :dup :clone #:nodoc:
341
+
342
+ private
343
+
344
+ # Setups memcached instance. Used for dynamic client reconfiguration
345
+ # when server pushes new config.
346
+ def setup(not_used)
347
+ servers = nodes.map do |n|
348
+ "#{n.hostname}:#{n.ports['proxy']}" if n.healthy?
349
+ end.compact
350
+ @memcached = ::Memcached.new(servers, @options)
351
+ @default_ttl = @memcached.options[:default_ttl]
352
+ end
353
+
354
+ def encode(value, mode)
355
+ case mode
356
+ when :document
357
+ Yajl::Encoder.encode(value)
358
+ when :marshal, :plain
359
+ value # encoding handled by memcached library internals
360
+ end
361
+ end
362
+
363
+ def decode(value, mode)
364
+ case mode
365
+ when :document
366
+ Yajl::Parser.parse(value)
367
+ when :marshal, :plain
368
+ value # encoding handled by memcached library internals
369
+ end
370
+ end
371
+ end
372
+ end
@@ -0,0 +1,49 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module Couchbase
19
+ class Node
20
+ attr_accessor :status, :hostname, :ports, :couch_api_base
21
+
22
+ def initialize(status, hostname, ports, couch_api_base)
23
+ @status = status
24
+ @hostname = hostname
25
+ @ports = ports
26
+ @couch_api_base = couch_api_base
27
+ end
28
+
29
+ %w(healthy warmup unhealthy).each do |status|
30
+ class_eval(<<-EOM)
31
+ def #{status}?
32
+ @status == '#{status}'
33
+ end
34
+ EOM
35
+ end
36
+
37
+ def have_couch_api?
38
+ !! @couch_api_base
39
+ end
40
+
41
+ def couch_api_base
42
+ if @couch_api_base
43
+ @couch_api_base
44
+ else
45
+ raise NotImplemented, "CouchDB API isn't available for the node #{@hostname}"
46
+ end
47
+ end
48
+ end
49
+ end