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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/HISTORY.markdown +50 -0
- data/LICENSE +201 -0
- data/README.markdown +135 -0
- data/Rakefile +27 -0
- data/couchbase.gemspec +28 -0
- data/lib/couchbase.rb +57 -0
- data/lib/couchbase/bucket.rb +138 -0
- data/lib/couchbase/couchdb.rb +107 -0
- data/lib/couchbase/document.rb +71 -0
- data/lib/couchbase/http_status.rb +118 -0
- data/lib/couchbase/latch.rb +71 -0
- data/lib/couchbase/memcached.rb +372 -0
- data/lib/couchbase/node.rb +49 -0
- data/lib/couchbase/rest_client.rb +124 -0
- data/lib/couchbase/version.rb +20 -0
- data/lib/couchbase/view.rb +182 -0
- data/test/setup.rb +28 -0
- data/test/support/buckets-config.json +843 -0
- data/test/support/sample_design_doc.json +9 -0
- data/test/test_bucket.rb +39 -0
- data/test/test_couchbase.rb +9 -0
- data/test/test_couchdb.rb +98 -0
- data/test/test_document.rb +11 -0
- data/test/test_latch.rb +88 -0
- data/test/test_memcached.rb +59 -0
- data/test/test_rest_client.rb +14 -0
- data/test/test_version.rb +7 -0
- data/test/test_view.rb +98 -0
- metadata +164 -0
@@ -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
|