couchbase-jruby-client 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.jrubyrc +722 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +203 -0
- data/README.md +349 -0
- data/Rakefile +10 -0
- data/couchbase-jruby-client.gemspec +31 -0
- data/lib/couchbase/async/callback.rb +19 -0
- data/lib/couchbase/async/queue.rb +26 -0
- data/lib/couchbase/async.rb +140 -0
- data/lib/couchbase/bucket.rb +556 -0
- data/lib/couchbase/cluster.rb +105 -0
- data/lib/couchbase/constants.rb +12 -0
- data/lib/couchbase/design_doc.rb +61 -0
- data/lib/couchbase/error.rb +43 -0
- data/lib/couchbase/jruby/couchbase_client.rb +22 -0
- data/lib/couchbase/jruby/future.rb +8 -0
- data/lib/couchbase/operations/arithmetic.rb +301 -0
- data/lib/couchbase/operations/delete.rb +104 -0
- data/lib/couchbase/operations/design_docs.rb +99 -0
- data/lib/couchbase/operations/get.rb +282 -0
- data/lib/couchbase/operations/stats.rb +26 -0
- data/lib/couchbase/operations/store.rb +461 -0
- data/lib/couchbase/operations/touch.rb +136 -0
- data/lib/couchbase/operations/unlock.rb +192 -0
- data/lib/couchbase/operations/utils.rb +44 -0
- data/lib/couchbase/operations.rb +27 -0
- data/lib/couchbase/query.rb +73 -0
- data/lib/couchbase/result.rb +43 -0
- data/lib/couchbase/transcoder.rb +77 -0
- data/lib/couchbase/utils.rb +62 -0
- data/lib/couchbase/version.rb +3 -0
- data/lib/couchbase/view.rb +367 -0
- data/lib/couchbase/view_row.rb +193 -0
- data/lib/couchbase.rb +157 -0
- data/lib/jars/commons-codec-1.5.jar +0 -0
- data/lib/jars/couchbase-client-1.2.0-javadoc.jar +0 -0
- data/lib/jars/couchbase-client-1.2.0-sources.jar +0 -0
- data/lib/jars/couchbase-client-1.2.0.jar +0 -0
- data/lib/jars/httpcore-4.1.1.jar +0 -0
- data/lib/jars/httpcore-nio-4.1.1.jar +0 -0
- data/lib/jars/jettison-1.1.jar +0 -0
- data/lib/jars/netty-3.5.5.Final.jar +0 -0
- data/lib/jars/spymemcached-2.10.0-javadoc.jar +0 -0
- data/lib/jars/spymemcached-2.10.0-sources.jar +0 -0
- data/lib/jars/spymemcached-2.10.0.jar +0 -0
- data/test/profile/.gitignore +1 -0
- data/test/profile/.jrubyrc +722 -0
- data/test/profile/Gemfile +6 -0
- data/test/profile/benchmark.rb +168 -0
- data/test/profile/profile.rb +59 -0
- data/test/setup.rb +203 -0
- data/test/test_arithmetic.rb +177 -0
- data/test/test_async.rb +324 -0
- data/test/test_bucket.rb +213 -0
- data/test/test_cas.rb +79 -0
- data/test/test_couchbase.rb +29 -0
- data/test/test_couchbase_rails_cache_store.rb +341 -0
- data/test/test_delete.rb +125 -0
- data/test/test_design_docs.rb +72 -0
- data/test/test_errors.rb +82 -0
- data/test/test_format.rb +161 -0
- data/test/test_get.rb +417 -0
- data/test/test_query.rb +23 -0
- data/test/test_stats.rb +57 -0
- data/test/test_store.rb +213 -0
- data/test/test_timer.rb +43 -0
- data/test/test_touch.rb +97 -0
- data/test/test_unlock.rb +121 -0
- data/test/test_utils.rb +58 -0
- data/test/test_version.rb +53 -0
- data/test/test_view.rb +94 -0
- metadata +255 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'couchbase/async/callback'
|
2
|
+
require 'couchbase/async/queue'
|
3
|
+
|
4
|
+
module Couchbase
|
5
|
+
module Async
|
6
|
+
|
7
|
+
def async?
|
8
|
+
!!async
|
9
|
+
end
|
10
|
+
|
11
|
+
def async
|
12
|
+
Thread.current[:bucket_async] ||= @async
|
13
|
+
end
|
14
|
+
|
15
|
+
def async=(val)
|
16
|
+
Thread.current[:bucket_async] = val
|
17
|
+
end
|
18
|
+
|
19
|
+
def running?
|
20
|
+
!!running
|
21
|
+
end
|
22
|
+
|
23
|
+
def running
|
24
|
+
Thread.current[:bucket_running] ||= false
|
25
|
+
end
|
26
|
+
|
27
|
+
def running=(val)
|
28
|
+
Thread.current[:bucket_running] = val
|
29
|
+
end
|
30
|
+
|
31
|
+
def async_queue
|
32
|
+
Thread.current[:bucket_async_queue] ||= Couchbase::Async::Queue.new(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def end_async_queue
|
36
|
+
Thread.current[:bucket_async_queue] = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
public
|
40
|
+
|
41
|
+
# Run the event loop.
|
42
|
+
#
|
43
|
+
# @since 1.0.0
|
44
|
+
#
|
45
|
+
# @param [Hash] options The options for operation for connection
|
46
|
+
# @option options [Fixnum] :send_threshold (0) if the internal command
|
47
|
+
# buffer will exceeds this value, then the library will start network
|
48
|
+
# interaction and block the current thread until all scheduled commands
|
49
|
+
# will be completed.
|
50
|
+
#
|
51
|
+
# @yieldparam [Bucket] bucket the bucket instance
|
52
|
+
#
|
53
|
+
# @example Use block to run the loop
|
54
|
+
# c = Couchbase.new
|
55
|
+
# c.run do
|
56
|
+
# c.get("foo") {|ret| puts ret.value}
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# @example Use lambda to run the loop
|
60
|
+
# c = Couchbase.new
|
61
|
+
# operations = lambda do |c|
|
62
|
+
# c.get("foo") {|ret| puts ret.value}
|
63
|
+
# end
|
64
|
+
# c.run(&operations)
|
65
|
+
#
|
66
|
+
# @example Use threshold to send out commands automatically
|
67
|
+
# c = Couchbase.connect
|
68
|
+
# sent = 0
|
69
|
+
# c.run(:send_threshold => 8192) do # 8Kb
|
70
|
+
# c.set("foo1", "x" * 100) {|r| sent += 1}
|
71
|
+
# # 128 bytes buffered, sent is 0 now
|
72
|
+
# c.set("foo2", "x" * 10000) {|r| sent += 1}
|
73
|
+
# # 10028 bytes added, sent is 2 now
|
74
|
+
# c.set("foo3", "x" * 100) {|r| sent += 1}
|
75
|
+
# end
|
76
|
+
# # all commands were executed and sent is 3 now
|
77
|
+
#
|
78
|
+
# @example Use {Couchbase::Bucket#run} without block for async connection
|
79
|
+
# c = Couchbase.new(:async => true)
|
80
|
+
# c.run # ensure that instance connected
|
81
|
+
# c.set("foo", "bar"){|r| puts r.cas}
|
82
|
+
# c.run
|
83
|
+
#
|
84
|
+
# @return [nil]
|
85
|
+
#
|
86
|
+
# @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
|
87
|
+
#
|
88
|
+
def run(options = {})
|
89
|
+
do_async_setup(block_given?)
|
90
|
+
yield(self)
|
91
|
+
async_queue.join
|
92
|
+
|
93
|
+
# TODO: deal with exceptions
|
94
|
+
nil
|
95
|
+
ensure
|
96
|
+
do_async_ensure
|
97
|
+
end
|
98
|
+
|
99
|
+
def run_async(options = {})
|
100
|
+
do_async_setup(block_given?)
|
101
|
+
yield(self)
|
102
|
+
nil
|
103
|
+
ensure
|
104
|
+
do_async_ensure
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def do_async_setup(block_given)
|
110
|
+
raise LocalJumpError.new('block required for async run') unless block_given
|
111
|
+
# TODO: check for connection
|
112
|
+
raise Error::Invalid.new('nested #run') if running?
|
113
|
+
# TOOD: deal with thresholds
|
114
|
+
|
115
|
+
self.async = true
|
116
|
+
self.running = true
|
117
|
+
end
|
118
|
+
|
119
|
+
def do_async_ensure
|
120
|
+
self.async = false
|
121
|
+
self.running = false
|
122
|
+
end_async_queue
|
123
|
+
end
|
124
|
+
|
125
|
+
def register_future(future, options, &block)
|
126
|
+
if async_queue
|
127
|
+
async_queue.add_future(future, options, &block)
|
128
|
+
else
|
129
|
+
register_callback(future, &block)
|
130
|
+
end
|
131
|
+
future
|
132
|
+
end
|
133
|
+
|
134
|
+
def register_callback(future, &block)
|
135
|
+
callback = Couchbase::Callback.new(:set, &block)
|
136
|
+
future.addListener(callback)
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,556 @@
|
|
1
|
+
module Couchbase
|
2
|
+
|
3
|
+
class Bucket
|
4
|
+
|
5
|
+
java_import java.io.IOException
|
6
|
+
java_import java.net.SocketAddress
|
7
|
+
java_import java.net.URI
|
8
|
+
java_import java.net.URISyntaxException
|
9
|
+
java_import java.util.ArrayList
|
10
|
+
java_import java.util.LinkedList
|
11
|
+
java_import java.util.List
|
12
|
+
java_import java.util.concurrent.Future
|
13
|
+
java_import java.util.concurrent.TimeUnit
|
14
|
+
java_import com.couchbase.client.CouchbaseClient
|
15
|
+
java_import com.couchbase.client.CouchbaseConnectionFactory
|
16
|
+
java_import com.couchbase.client.CouchbaseConnectionFactoryBuilder
|
17
|
+
|
18
|
+
include Couchbase::Operations
|
19
|
+
include Couchbase::Async
|
20
|
+
|
21
|
+
attr_accessor :quiet, :hostname, :port, :pool, :bucket, :username,
|
22
|
+
:password, :default_ttl, :timeout,
|
23
|
+
:default_arithmetic_init, :transcoder
|
24
|
+
|
25
|
+
attr_reader :client, :key_prefix, :default_format
|
26
|
+
|
27
|
+
# Initialize new Bucket.
|
28
|
+
#
|
29
|
+
# @since 1.0.0
|
30
|
+
#
|
31
|
+
# @overload initialize(url, options = {})
|
32
|
+
# Initialize bucket using URI of the cluster and options. It is possible
|
33
|
+
# to override some parts of URI using the options keys (e.g. :host or
|
34
|
+
# :port)
|
35
|
+
#
|
36
|
+
# @param [String] url The full URL of management API of the cluster.
|
37
|
+
# @param [Hash] options The options for connection. See options definition
|
38
|
+
# below.
|
39
|
+
#
|
40
|
+
# @overload initialize(options = {})
|
41
|
+
# Initialize bucket using options only.
|
42
|
+
#
|
43
|
+
# @param [Hash] options The options for operation for connection
|
44
|
+
# @option options [Array] :node_list (nil) the list of nodes to connect
|
45
|
+
# to. If specified it takes precedence over +:host+ option. The list
|
46
|
+
# must be array of strings in form of host names or host names with
|
47
|
+
# ports (in first case port 8091 will be used, see examples).
|
48
|
+
# @option options [String] :host ("localhost") the hostname or IP address
|
49
|
+
# of the node
|
50
|
+
# @option options [Fixnum] :port (8091) the port of the managemenent API
|
51
|
+
# @option options [String] :pool ("default") the pool name
|
52
|
+
# @option options [String] :bucket ("default") the bucket name
|
53
|
+
# @option options [Fixnum] :default_ttl (0) the TTL used by default during
|
54
|
+
# storing key-value pairs.
|
55
|
+
# @option options [Fixnum] :default_flags (0) the default flags.
|
56
|
+
# @option options [Symbol] :default_format (:document) the format, which
|
57
|
+
# will be used for values by default. Note that changing format will
|
58
|
+
# amend flags. (see {Bucket#default_format})
|
59
|
+
# @option options [String] :username (nil) the user name to connect to the
|
60
|
+
# cluster. Used to authenticate on management API. The username could
|
61
|
+
# be skipped for protected buckets, the bucket name will be used
|
62
|
+
# instead.
|
63
|
+
# @option options [String] :password (nil) the password of the user.
|
64
|
+
# @option options [true, false] :quiet (false) the flag controlling if raising
|
65
|
+
# exception when the client executes operations on non-existent keys. If it
|
66
|
+
# is +true+ it will raise {Couchbase::Error::NotFound} exceptions. The
|
67
|
+
# default behaviour is to return +nil+ value silently (might be useful in
|
68
|
+
# Rails cache).
|
69
|
+
# @option options [Symbol] :environment (:production) the mode of the
|
70
|
+
# connection. Currently it influences only on design documents set. If
|
71
|
+
# the environment is +:development+, you will able to get design
|
72
|
+
# documents with 'dev_' prefix, otherwise (in +:production+ mode) the
|
73
|
+
# library will hide them from you.
|
74
|
+
# @option options [String] :key_prefix (nil) the prefix string which will
|
75
|
+
# be prepended to each key before sending out, and sripped before
|
76
|
+
# returning back to the application.
|
77
|
+
# @option options [Fixnum] :timeout (2500000) the timeout for IO
|
78
|
+
# operations (in microseconds)
|
79
|
+
# @option options [Fixnum, true] :default_arithmetic_init (0) the default
|
80
|
+
# initial value for arithmetic operations. Setting this option to any
|
81
|
+
# non positive number forces creation missing keys with given default
|
82
|
+
# value. Setting it to +true+ will use zero as initial value. (see
|
83
|
+
# {Bucket#incr} and {Bucket#decr}).
|
84
|
+
# @option options [Symbol] :engine (:default) the IO engine to use
|
85
|
+
# Currently following engines are supported:
|
86
|
+
# :default :: Built-in engine (multi-thread friendly)
|
87
|
+
# :libevent :: libevent IO plugin from libcouchbase (optional)
|
88
|
+
# :libev :: libev IO plugin from libcouchbase (optional)
|
89
|
+
# :eventmachine :: EventMachine plugin (builtin, but requires EM gem and ruby 1.9+)
|
90
|
+
# @option options [true, false] :async (false) If true, the
|
91
|
+
# connection instance will be considered always asynchronous and
|
92
|
+
# IO interaction will be occured only when {Couchbase::Bucket#run}
|
93
|
+
# called. See {Couchbase::Bucket#on_connect} to hook your code
|
94
|
+
# after the instance will be connected.
|
95
|
+
#
|
96
|
+
# @example Initialize connection using default options
|
97
|
+
# Couchbase.new
|
98
|
+
#
|
99
|
+
# @example Select custom bucket
|
100
|
+
# Couchbase.new(:bucket => 'foo')
|
101
|
+
# Couchbase.new('http://localhost:8091/pools/default/buckets/foo')
|
102
|
+
#
|
103
|
+
# @example Connect to protected bucket
|
104
|
+
# Couchbase.new(:bucket => 'protected', :username => 'protected', :password => 'secret')
|
105
|
+
# Couchbase.new('http://localhost:8091/pools/default/buckets/protected',
|
106
|
+
# :username => 'protected', :password => 'secret')
|
107
|
+
#
|
108
|
+
# @example Use list of nodes, in case some nodes might be dead
|
109
|
+
# Couchbase.new(:node_list => ['example.com:8091', 'example.org:8091', 'example.net'])
|
110
|
+
#
|
111
|
+
# @raise [Couchbase::Error::BucketNotFound] if there is no such bucket to
|
112
|
+
# connect to
|
113
|
+
#
|
114
|
+
# @raise [Couchbase::Error::Connect] if the socket wasn't accessible
|
115
|
+
# (doesn't accept connections or doesn't respond in time)
|
116
|
+
#
|
117
|
+
# @return [Bucket]
|
118
|
+
#
|
119
|
+
def initialize(url = nil, options = {})
|
120
|
+
default_options = {
|
121
|
+
type: nil,
|
122
|
+
quiet: false,
|
123
|
+
hostname: 'localhost',
|
124
|
+
port: 8091,
|
125
|
+
pool: 'default',
|
126
|
+
bucket: 'default',
|
127
|
+
password: '',
|
128
|
+
engine: nil,
|
129
|
+
default_ttl: 0,
|
130
|
+
async: false,
|
131
|
+
default_arithmetic_init: 0,
|
132
|
+
default_flags: 0,
|
133
|
+
default_format: :document,
|
134
|
+
default_observe_timeout: 2500000,
|
135
|
+
on_error: nil,
|
136
|
+
on_connect: nil,
|
137
|
+
timeout: 0,
|
138
|
+
environment: nil,
|
139
|
+
key_prefix: nil,
|
140
|
+
node_list: nil,
|
141
|
+
destroying: 0,
|
142
|
+
connected: 0,
|
143
|
+
on_connect_proc: nil,
|
144
|
+
async_disconnect_hook_set: 0,
|
145
|
+
connected: false
|
146
|
+
}
|
147
|
+
|
148
|
+
url_options = if url.is_a? String
|
149
|
+
fail ArgumentError.new unless url =~ /^http:\/\//
|
150
|
+
|
151
|
+
uri = URI.new(url)
|
152
|
+
|
153
|
+
{
|
154
|
+
host: uri.host,
|
155
|
+
port: uri.port,
|
156
|
+
}.merge(path_to_pool_and_bucket(uri.path))
|
157
|
+
elsif url.nil?
|
158
|
+
{}
|
159
|
+
else
|
160
|
+
url
|
161
|
+
end
|
162
|
+
|
163
|
+
connection_options = default_options.merge(options).merge(url_options)
|
164
|
+
|
165
|
+
connection_options.each_pair do |key, value|
|
166
|
+
instance_variable_set("@#{key}", value)
|
167
|
+
end
|
168
|
+
|
169
|
+
@transcoders = {
|
170
|
+
document: Transcoder::Document.new,
|
171
|
+
marshal: Transcoder::Marshal.new,
|
172
|
+
plain: Transcoder::Plain.new
|
173
|
+
}
|
174
|
+
|
175
|
+
@transcoder = @transcoders[@default_format]
|
176
|
+
|
177
|
+
connect unless async?
|
178
|
+
end
|
179
|
+
|
180
|
+
def quiet?
|
181
|
+
!!quiet
|
182
|
+
end
|
183
|
+
|
184
|
+
def host
|
185
|
+
hostname
|
186
|
+
end
|
187
|
+
|
188
|
+
def connect
|
189
|
+
uris = if @node_list
|
190
|
+
Array(@node_list).map { |n| URI.new(n) }
|
191
|
+
else
|
192
|
+
Array(URI.new(base_url))
|
193
|
+
end
|
194
|
+
|
195
|
+
begin
|
196
|
+
builder = CouchbaseConnectionFactoryBuilder.new
|
197
|
+
builder.setTranscoder(@transcoder)
|
198
|
+
connection_factory = builder.buildCouchbaseConnection(uris, bucket.to_java_string, password.to_java_string)
|
199
|
+
@client = CouchbaseClient.new(connection_factory)
|
200
|
+
@connected = true
|
201
|
+
rescue Java::ComCouchbaseClientVbucket::ConfigurationException
|
202
|
+
fail Couchbase::Error::Auth
|
203
|
+
rescue java.net.ConnectException => e
|
204
|
+
fail Couchbase::Error::Connect
|
205
|
+
end
|
206
|
+
|
207
|
+
self
|
208
|
+
end
|
209
|
+
alias_method :reconnect, :connect
|
210
|
+
|
211
|
+
def authority
|
212
|
+
"#{hostname}:#{port}"
|
213
|
+
end
|
214
|
+
|
215
|
+
def base_url
|
216
|
+
"http://#{authority}/pools"
|
217
|
+
end
|
218
|
+
|
219
|
+
def url
|
220
|
+
"http://#{authority}/pools/#{pool}/buckets/#{bucket}/"
|
221
|
+
end
|
222
|
+
|
223
|
+
def connected?
|
224
|
+
@connected
|
225
|
+
end
|
226
|
+
|
227
|
+
def disconnect
|
228
|
+
if connected?
|
229
|
+
@client.shutdown(3, TimeUnit::SECONDS)
|
230
|
+
@client = nil
|
231
|
+
@connection_factory = nil
|
232
|
+
@connected = false
|
233
|
+
else
|
234
|
+
fail Couchbase::Error::Connect
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def on_connect(&block)
|
239
|
+
@on_connect = block
|
240
|
+
end
|
241
|
+
|
242
|
+
def on_error(&block)
|
243
|
+
@on_error = block
|
244
|
+
end
|
245
|
+
|
246
|
+
def version
|
247
|
+
{}.tap do |hash|
|
248
|
+
@client.getVersions.to_hash.each_pair do |ip, ver|
|
249
|
+
hash[ip.to_s] = ver
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Compare and swap value.
|
255
|
+
#
|
256
|
+
# @since 1.0.0
|
257
|
+
#
|
258
|
+
# Reads a key's value from the server and yields it to a block. Replaces
|
259
|
+
# the key's value with the result of the block as long as the key hasn't
|
260
|
+
# been updated in the meantime, otherwise raises
|
261
|
+
# {Couchbase::Error::KeyExists}. CAS stands for "compare and swap", and
|
262
|
+
# avoids the need for manual key mutexing. Read more info here:
|
263
|
+
#
|
264
|
+
# In asynchronous mode it will yield result twice, first for
|
265
|
+
# {Bucket#get} with {Result#operation} equal to +:get+ and
|
266
|
+
# second time for {Bucket#set} with {Result#operation} equal to +:set+.
|
267
|
+
#
|
268
|
+
# @see http://couchbase.com/docs/memcached-api/memcached-api-protocol-text_cas.html
|
269
|
+
#
|
270
|
+
# @param [String, Symbol] key
|
271
|
+
#
|
272
|
+
# @param [Hash] options the options for "swap" part
|
273
|
+
# @option options [Fixnum] :ttl (self.default_ttl) the time to live of this key
|
274
|
+
# @option options [Symbol] :format (self.default_format) format of the value
|
275
|
+
# @option options [Fixnum] :flags (self.default_flags) flags for this key
|
276
|
+
#
|
277
|
+
# @yieldparam [Object, Result] value old value in synchronous mode and
|
278
|
+
# +Result+ object in asynchronous mode.
|
279
|
+
# @yieldreturn [Object] new value.
|
280
|
+
#
|
281
|
+
# @raise [Couchbase::Error::KeyExists] if the key was updated before the the
|
282
|
+
# code in block has been completed (the CAS value has been changed).
|
283
|
+
# @raise [ArgumentError] if the block is missing for async mode
|
284
|
+
#
|
285
|
+
# @example Implement append to JSON encoded value
|
286
|
+
#
|
287
|
+
# c.default_format = :document
|
288
|
+
# c.set("foo", {"bar" => 1})
|
289
|
+
# c.cas("foo") do |val|
|
290
|
+
# val["baz"] = 2
|
291
|
+
# val
|
292
|
+
# end
|
293
|
+
# c.get("foo") #=> {"bar" => 1, "baz" => 2}
|
294
|
+
#
|
295
|
+
# @example Append JSON encoded value asynchronously
|
296
|
+
#
|
297
|
+
# c.default_format = :document
|
298
|
+
# c.set("foo", {"bar" => 1})
|
299
|
+
# c.run do
|
300
|
+
# c.cas("foo") do |val|
|
301
|
+
# case val.operation
|
302
|
+
# when :get
|
303
|
+
# val["baz"] = 2
|
304
|
+
# val
|
305
|
+
# when :set
|
306
|
+
# # verify all is ok
|
307
|
+
# puts "error: #{ret.error.inspect}" unless ret.success?
|
308
|
+
# end
|
309
|
+
# end
|
310
|
+
# end
|
311
|
+
# c.get("foo") #=> {"bar" => 1, "baz" => 2}
|
312
|
+
#
|
313
|
+
# @return [Fixnum] the CAS of new value
|
314
|
+
def cas(key, options = {})
|
315
|
+
if async?
|
316
|
+
block = Proc.new
|
317
|
+
get(key) do |ret|
|
318
|
+
val = block.call(ret) # get new value from caller
|
319
|
+
set(ret.key, val, options.merge(:cas => ret.cas, &block))
|
320
|
+
end
|
321
|
+
else
|
322
|
+
val, flags, ver = get(key, :extended => true)
|
323
|
+
val = yield(val) # get new value from caller
|
324
|
+
set(key, val, options.merge(:cas => ver))
|
325
|
+
end
|
326
|
+
end
|
327
|
+
alias :compare_and_swap :cas
|
328
|
+
|
329
|
+
# Delete contents of the bucket
|
330
|
+
#
|
331
|
+
# @see http://www.couchbase.com/docs/couchbase-manual-2.0/restapi-flushing-bucket.html
|
332
|
+
#
|
333
|
+
# @since 1.2.0.beta
|
334
|
+
#
|
335
|
+
# @yieldparam [Result] ret the object with +error+, +status+ and +operation+
|
336
|
+
# attributes.
|
337
|
+
#
|
338
|
+
# @raise [Couchbase::Error::Protocol] in case of an error is
|
339
|
+
# encountered. Check {Couchbase::Error::Base#status} for detailed code.
|
340
|
+
#
|
341
|
+
# @return [true] always return true (see raise section)
|
342
|
+
#
|
343
|
+
# @example Simple flush the bucket
|
344
|
+
# c.flush #=> true
|
345
|
+
#
|
346
|
+
# @example Asynchronous flush
|
347
|
+
# c.run do
|
348
|
+
# c.flush do |ret|
|
349
|
+
# ret.operation #=> :flush
|
350
|
+
# ret.success? #=> true
|
351
|
+
# ret.status #=> 200
|
352
|
+
# end
|
353
|
+
# end
|
354
|
+
def flush
|
355
|
+
if !async? && block_given?
|
356
|
+
sync_block_error
|
357
|
+
end
|
358
|
+
req = make_http_request("/pools/default/buckets/#{bucket}/controller/doFlush",
|
359
|
+
:type => :management, :method => :post, :extended => true)
|
360
|
+
res = nil
|
361
|
+
req.on_body do |r|
|
362
|
+
res = r
|
363
|
+
res.instance_variable_set("@operation", :flush)
|
364
|
+
yield(res) if block_given?
|
365
|
+
end
|
366
|
+
req.continue
|
367
|
+
true
|
368
|
+
end
|
369
|
+
|
370
|
+
# Create and register one-shot timer
|
371
|
+
#
|
372
|
+
# @return [Couchbase::Timer]
|
373
|
+
def create_timer(interval, &block)
|
374
|
+
Timer.new(self, interval, &block)
|
375
|
+
end
|
376
|
+
|
377
|
+
# Create and register periodic timer
|
378
|
+
#
|
379
|
+
# @return [Couchbase::Timer]
|
380
|
+
def create_periodic_timer(interval, &block)
|
381
|
+
Timer.new(self, interval, :periodic => true, &block)
|
382
|
+
end
|
383
|
+
|
384
|
+
# Wait for persistence condition
|
385
|
+
#
|
386
|
+
# @since 1.2.0.dp6
|
387
|
+
#
|
388
|
+
# This operation is useful when some confidence needed regarding the
|
389
|
+
# state of the keys. With two parameters +:replicated+ and +:persisted+
|
390
|
+
# it allows to set up the waiting rule.
|
391
|
+
#
|
392
|
+
# @param [String, Symbol, Array, Hash] keys The list of the keys to
|
393
|
+
# observe. Full form is hash with key-cas value pairs, but there are
|
394
|
+
# also shortcuts like just Array of keys or single key. CAS value
|
395
|
+
# needed to when you need to ensure that the storage persisted exactly
|
396
|
+
# the same version of the key you are asking to observe.
|
397
|
+
# @param [Hash] options The options for operation
|
398
|
+
# @option options [Fixnum] :timeout The timeout in microseconds
|
399
|
+
# @option options [Fixnum] :replicated How many replicas should receive
|
400
|
+
# the copy of the key.
|
401
|
+
# @option options [Fixnum] :persisted How many nodes should store the
|
402
|
+
# key on the disk.
|
403
|
+
#
|
404
|
+
# @raise [Couchbase::Error::Timeout] if the given time is up
|
405
|
+
#
|
406
|
+
# @return [Fixnum, Hash<String, Fixnum>] will return CAS value just like
|
407
|
+
# mutators or pairs key-cas in case of multiple keys.
|
408
|
+
def observe_and_wait(*keys, &block)
|
409
|
+
options = {:timeout => default_observe_timeout}
|
410
|
+
options.update(keys.pop) if keys.size > 1 && keys.last.is_a?(Hash)
|
411
|
+
verify_observe_options(options)
|
412
|
+
if block && !async?
|
413
|
+
raise ArgumentError, "synchronous mode doesn't support callbacks"
|
414
|
+
end
|
415
|
+
if keys.size == 0
|
416
|
+
raise ArgumentError, "at least one key is required"
|
417
|
+
end
|
418
|
+
if keys.size == 1 && keys[0].is_a?(Hash)
|
419
|
+
key_cas = keys[0]
|
420
|
+
else
|
421
|
+
key_cas = keys.flatten.reduce({}) do |h, kk|
|
422
|
+
h[kk] = nil # set CAS to nil
|
423
|
+
h
|
424
|
+
end
|
425
|
+
end
|
426
|
+
if async?
|
427
|
+
do_observe_and_wait(key_cas, options, &block)
|
428
|
+
else
|
429
|
+
res = do_observe_and_wait(key_cas, options, &block) while res.nil?
|
430
|
+
unless async?
|
431
|
+
if keys.size == 1 && (keys[0].is_a?(String) || keys[0].is_a?(Symbol))
|
432
|
+
return res.values.first
|
433
|
+
else
|
434
|
+
return res
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
private
|
441
|
+
|
442
|
+
def path_to_pool_and_bucket(path)
|
443
|
+
{}
|
444
|
+
end
|
445
|
+
|
446
|
+
def verify_observe_options(options)
|
447
|
+
unless num_replicas
|
448
|
+
raise Couchbase::Error::Libcouchbase, "cannot detect number of the replicas"
|
449
|
+
end
|
450
|
+
unless options[:persisted] || options[:replicated]
|
451
|
+
raise ArgumentError, "either :persisted or :replicated option must be set"
|
452
|
+
end
|
453
|
+
if options[:persisted] && !(1..num_replicas + 1).include?(options[:persisted])
|
454
|
+
raise ArgumentError, "persisted number should be in range (1..#{num_replicas + 1})"
|
455
|
+
end
|
456
|
+
if options[:replicated] && !(1..num_replicas).include?(options[:replicated])
|
457
|
+
raise ArgumentError, "replicated number should be in range (1..#{num_replicas})"
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
def do_observe_and_wait(keys, options, &block)
|
462
|
+
acc = Hash.new do |h, k|
|
463
|
+
h[k] = Hash.new(0)
|
464
|
+
h[k][:cas] = [keys[k]] # first position is for master node
|
465
|
+
h[k]
|
466
|
+
end
|
467
|
+
check_condition = lambda do
|
468
|
+
ok = catch :break do
|
469
|
+
acc.each do |key, stats|
|
470
|
+
master = stats[:cas][0]
|
471
|
+
if master.nil?
|
472
|
+
# master node doesn't have the key
|
473
|
+
throw :break
|
474
|
+
end
|
475
|
+
if options[:persisted] && (stats[:persisted] < options[:persisted] ||
|
476
|
+
stats[:cas].count(master) != options[:persisted])
|
477
|
+
throw :break
|
478
|
+
end
|
479
|
+
if options[:replicated] && (stats[:replicated] < options[:replicated] ||
|
480
|
+
stats[:cas].count(master) != options[:replicated] + 1)
|
481
|
+
throw :break
|
482
|
+
end
|
483
|
+
end
|
484
|
+
true
|
485
|
+
end
|
486
|
+
if ok
|
487
|
+
if async?
|
488
|
+
options[:timer].cancel if options[:timer]
|
489
|
+
keys.each do |k, _|
|
490
|
+
block.call(Result.new(:key => k,
|
491
|
+
:cas => acc[k][:cas][0],
|
492
|
+
:operation => :observe_and_wait))
|
493
|
+
end
|
494
|
+
return :async
|
495
|
+
else
|
496
|
+
return keys.inject({}){|res, (k, _)| res[k] = acc[k][:cas][0]; res}
|
497
|
+
end
|
498
|
+
else
|
499
|
+
options[:timeout] /= 2
|
500
|
+
if options[:timeout] > 0
|
501
|
+
if async?
|
502
|
+
options[:timer] = create_timer(options[:timeout]) do
|
503
|
+
do_observe_and_wait(keys, options, &block)
|
504
|
+
end
|
505
|
+
return :async
|
506
|
+
else
|
507
|
+
# do wait for timeout
|
508
|
+
run { create_timer(options[:timeout]){} }
|
509
|
+
# return nil to avoid recursive call
|
510
|
+
return nil
|
511
|
+
end
|
512
|
+
else
|
513
|
+
err = Couchbase::Error::Timeout.new("the observe request was timed out")
|
514
|
+
err.instance_variable_set("@operation", :observe_and_wait)
|
515
|
+
if async?
|
516
|
+
keys.each do |k, _|
|
517
|
+
block.call(Result.new(:key => k,
|
518
|
+
:cas => acc[k][:cas][0],
|
519
|
+
:operation => :observe_and_wait,
|
520
|
+
:error => err))
|
521
|
+
end
|
522
|
+
return :async
|
523
|
+
else
|
524
|
+
err.instance_variable_set("@key", keys.keys)
|
525
|
+
raise err
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
collect = lambda do |results|
|
531
|
+
results.each do |res|
|
532
|
+
if res.completed?
|
533
|
+
check_condition.call if async?
|
534
|
+
else
|
535
|
+
if res.from_master?
|
536
|
+
acc[res.key][:cas][0] = res.cas
|
537
|
+
else
|
538
|
+
acc[res.key][:cas] << res.cas
|
539
|
+
end
|
540
|
+
acc[res.key][res.status] += 1
|
541
|
+
if res.status == :persisted
|
542
|
+
acc[res.key][:replicated] += 1
|
543
|
+
end
|
544
|
+
end
|
545
|
+
end
|
546
|
+
end
|
547
|
+
if async?
|
548
|
+
observe(keys.keys, options, &collect)
|
549
|
+
else
|
550
|
+
observe(keys.keys, options).each{|_, v| collect.call(v)}
|
551
|
+
check_condition.call
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
end
|