couchbase 0.9.7

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.
@@ -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