couchbase 1.3.4-x64-mingw32
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 +15 -0
- data/.travis.yml +22 -0
- data/.yardopts +5 -0
- data/CONTRIBUTING.markdown +75 -0
- data/Gemfile +4 -0
- data/LICENSE +201 -0
- data/Makefile +3 -0
- data/README.markdown +649 -0
- data/RELEASE_NOTES.markdown +796 -0
- data/Rakefile +20 -0
- data/couchbase.gemspec +49 -0
- data/examples/chat-em/Gemfile +7 -0
- data/examples/chat-em/README.markdown +45 -0
- data/examples/chat-em/server.rb +82 -0
- data/examples/chat-goliath-grape/Gemfile +5 -0
- data/examples/chat-goliath-grape/README.markdown +50 -0
- data/examples/chat-goliath-grape/app.rb +67 -0
- data/examples/chat-goliath-grape/config/app.rb +20 -0
- data/examples/transcoders/Gemfile +3 -0
- data/examples/transcoders/README.markdown +59 -0
- data/examples/transcoders/cb-zcat +40 -0
- data/examples/transcoders/cb-zcp +45 -0
- data/examples/transcoders/gzip_transcoder.rb +49 -0
- data/examples/transcoders/options.rb +54 -0
- data/ext/couchbase_ext/.gitignore +4 -0
- data/ext/couchbase_ext/arguments.c +956 -0
- data/ext/couchbase_ext/arithmetic.c +307 -0
- data/ext/couchbase_ext/bucket.c +1370 -0
- data/ext/couchbase_ext/context.c +65 -0
- data/ext/couchbase_ext/couchbase_ext.c +1364 -0
- data/ext/couchbase_ext/couchbase_ext.h +644 -0
- data/ext/couchbase_ext/delete.c +163 -0
- data/ext/couchbase_ext/eventmachine_plugin.c +452 -0
- data/ext/couchbase_ext/extconf.rb +168 -0
- data/ext/couchbase_ext/get.c +316 -0
- data/ext/couchbase_ext/gethrtime.c +129 -0
- data/ext/couchbase_ext/http.c +432 -0
- data/ext/couchbase_ext/multithread_plugin.c +1090 -0
- data/ext/couchbase_ext/observe.c +171 -0
- data/ext/couchbase_ext/plugin_common.c +171 -0
- data/ext/couchbase_ext/result.c +129 -0
- data/ext/couchbase_ext/stats.c +163 -0
- data/ext/couchbase_ext/store.c +542 -0
- data/ext/couchbase_ext/timer.c +192 -0
- data/ext/couchbase_ext/touch.c +186 -0
- data/ext/couchbase_ext/unlock.c +176 -0
- data/ext/couchbase_ext/utils.c +551 -0
- data/ext/couchbase_ext/version.c +142 -0
- data/lib/action_dispatch/middleware/session/couchbase_store.rb +38 -0
- data/lib/active_support/cache/couchbase_store.rb +430 -0
- data/lib/couchbase.rb +155 -0
- data/lib/couchbase/bucket.rb +457 -0
- data/lib/couchbase/cluster.rb +119 -0
- data/lib/couchbase/connection_pool.rb +58 -0
- data/lib/couchbase/constants.rb +12 -0
- data/lib/couchbase/result.rb +26 -0
- data/lib/couchbase/transcoder.rb +120 -0
- data/lib/couchbase/utils.rb +62 -0
- data/lib/couchbase/version.rb +21 -0
- data/lib/couchbase/view.rb +506 -0
- data/lib/couchbase/view_row.rb +272 -0
- data/lib/ext/multi_json_fix.rb +56 -0
- data/lib/rack/session/couchbase.rb +108 -0
- data/tasks/benchmark.rake +6 -0
- data/tasks/compile.rake +158 -0
- data/tasks/test.rake +100 -0
- data/tasks/util.rake +21 -0
- data/test/profile/.gitignore +1 -0
- data/test/profile/Gemfile +6 -0
- data/test/profile/benchmark.rb +195 -0
- data/test/setup.rb +178 -0
- data/test/test_arithmetic.rb +185 -0
- data/test/test_async.rb +316 -0
- data/test/test_bucket.rb +250 -0
- data/test/test_cas.rb +235 -0
- data/test/test_couchbase.rb +77 -0
- data/test/test_couchbase_connection_pool.rb +77 -0
- data/test/test_couchbase_rails_cache_store.rb +361 -0
- data/test/test_delete.rb +120 -0
- data/test/test_errors.rb +82 -0
- data/test/test_eventmachine.rb +70 -0
- data/test/test_format.rb +164 -0
- data/test/test_get.rb +407 -0
- data/test/test_stats.rb +57 -0
- data/test/test_store.rb +216 -0
- data/test/test_timer.rb +42 -0
- data/test/test_touch.rb +97 -0
- data/test/test_unlock.rb +119 -0
- data/test/test_utils.rb +58 -0
- data/test/test_version.rb +52 -0
- metadata +336 -0
data/lib/couchbase.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Author:: Couchbase <info@couchbase.com>
|
2
|
+
# Copyright:: 2011, 2012 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 'couchbase/version'
|
19
|
+
require 'yaji'
|
20
|
+
require 'uri'
|
21
|
+
require 'couchbase/transcoder'
|
22
|
+
require 'couchbase_ext'
|
23
|
+
require 'couchbase/constants'
|
24
|
+
require 'couchbase/utils'
|
25
|
+
require 'couchbase/bucket'
|
26
|
+
require 'couchbase/view_row'
|
27
|
+
require 'couchbase/view'
|
28
|
+
require 'couchbase/result'
|
29
|
+
require 'couchbase/cluster'
|
30
|
+
|
31
|
+
|
32
|
+
# Couchbase ruby client
|
33
|
+
module Couchbase
|
34
|
+
|
35
|
+
if RUBY_VERSION.to_f >= 1.9
|
36
|
+
autoload(:ConnectionPool, 'couchbase/connection_pool')
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
# The method +connect+ initializes new Bucket instance with all arguments passed.
|
41
|
+
#
|
42
|
+
# @since 1.0.0
|
43
|
+
#
|
44
|
+
# @see Bucket#initialize
|
45
|
+
#
|
46
|
+
# @example Use default values for all options
|
47
|
+
# Couchbase.connect
|
48
|
+
#
|
49
|
+
# @example Establish connection with couchbase default pool and default bucket
|
50
|
+
# Couchbase.connect("http://localhost:8091/pools/default")
|
51
|
+
#
|
52
|
+
# @example Select custom bucket
|
53
|
+
# Couchbase.connect("http://localhost:8091/pools/default", :bucket => 'blog')
|
54
|
+
#
|
55
|
+
# @example Specify bucket credentials
|
56
|
+
# Couchbase.connect("http://localhost:8091/pools/default", :bucket => 'blog', :username => 'bucket', :password => 'secret')
|
57
|
+
#
|
58
|
+
# @example Use URL notation
|
59
|
+
# Couchbase.connect("http://bucket:secret@localhost:8091/pools/default/buckets/blog")
|
60
|
+
#
|
61
|
+
# @return [Bucket] connection instance
|
62
|
+
def connect(*options)
|
63
|
+
Bucket.new(*(options.flatten))
|
64
|
+
end
|
65
|
+
alias :new :connect
|
66
|
+
|
67
|
+
# Default connection options
|
68
|
+
#
|
69
|
+
# @since 1.1.0
|
70
|
+
#
|
71
|
+
# @example Using {Couchbase#connection_options} to change the bucket
|
72
|
+
# Couchbase.connection_options = {:bucket => 'blog'}
|
73
|
+
# Couchbase.bucket.name #=> "blog"
|
74
|
+
#
|
75
|
+
# @return [Hash, String]
|
76
|
+
attr_accessor :connection_options
|
77
|
+
|
78
|
+
# @private the thread local storage
|
79
|
+
def thread_storage
|
80
|
+
Thread.current[:couchbase] ||= { :pid => Process.pid, :bucket => {} }
|
81
|
+
end
|
82
|
+
|
83
|
+
# @private resets thread local storage if process ids don't match
|
84
|
+
# see 13.3.1: http://www.modrails.com/documentation/Users%20guide%20Apache.html
|
85
|
+
def verify_connection!
|
86
|
+
reset_thread_storage! if thread_storage[:pid] != Process.pid
|
87
|
+
end
|
88
|
+
|
89
|
+
# @private resets thread local storage
|
90
|
+
def reset_thread_storage!
|
91
|
+
Thread.current[:couchbase] = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
# The connection instance for current thread
|
95
|
+
#
|
96
|
+
# @since 1.1.0
|
97
|
+
#
|
98
|
+
# @see Couchbase.connection_options
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# Couchbase.bucket.set("foo", "bar")
|
102
|
+
#
|
103
|
+
# @example Set connection options using Hash
|
104
|
+
# Couchbase.connection_options = {:node_list => ["example.com:8091"]}
|
105
|
+
# Couchbase.bucket("slot1").set("foo", "bar")
|
106
|
+
# Couchbase.bucket("slot1").bucket #=> "default"
|
107
|
+
# Couchbase.connection_options[:bucket] = "test"
|
108
|
+
# Couchbase.bucket("slot2").bucket #=> "test"
|
109
|
+
#
|
110
|
+
# @example Set connection options using URI
|
111
|
+
# Couchbase.connection_options = "http://example.com:8091/pools"
|
112
|
+
# Couchbase.bucket("slot1").set("foo", "bar")
|
113
|
+
# Couchbase.bucket("slot1").bucket #=> "default"
|
114
|
+
# Couchbase.connection_options = "http://example.com:8091/pools/buckets/test"
|
115
|
+
# Couchbase.bucket("slot2").bucket #=> "test"
|
116
|
+
#
|
117
|
+
# @example Use named slots to keep a connection
|
118
|
+
# Couchbase.connection_options = {
|
119
|
+
# :node_list => ["example.com", "example.org"],
|
120
|
+
# :bucket => "users"
|
121
|
+
# }
|
122
|
+
# Couchbase.bucket("users").set("john", {"balance" => 0})
|
123
|
+
# Couchbase.connection_options[:bucket] = "orders"
|
124
|
+
# Couchbase.bucket("other").set("john:1", {"products" => [42, 66]})
|
125
|
+
#
|
126
|
+
# @return [Bucket]
|
127
|
+
def bucket(name = nil)
|
128
|
+
verify_connection!
|
129
|
+
name ||= case @connection_options
|
130
|
+
when Hash
|
131
|
+
@connection_options[:bucket]
|
132
|
+
when String
|
133
|
+
path = URI.parse(@connection_options).path
|
134
|
+
path[%r(^(/pools/([A-Za-z0-9_.-]+)(/buckets/([A-Za-z0-9_.-]+))?)?), 3] || "default"
|
135
|
+
else
|
136
|
+
"default"
|
137
|
+
end
|
138
|
+
thread_storage[:bucket][name] ||= connect(connection_options)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Set a connection instance for current thread
|
142
|
+
#
|
143
|
+
# @since 1.1.0
|
144
|
+
#
|
145
|
+
# @return [Bucket]
|
146
|
+
def bucket=(connection, name = nil)
|
147
|
+
verify_connection!
|
148
|
+
name ||= @connection_options && @connection_options[:bucket] || "default"
|
149
|
+
thread_storage[:bucket][name] = connection
|
150
|
+
end
|
151
|
+
alias set_bucket bucket=
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,457 @@
|
|
1
|
+
# Author:: Couchbase <info@couchbase.com>
|
2
|
+
# Copyright:: 2011, 2012 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
|
+
|
20
|
+
class Bucket
|
21
|
+
|
22
|
+
# Compare and swap value.
|
23
|
+
#
|
24
|
+
# @since 1.0.0
|
25
|
+
#
|
26
|
+
# Reads a key's value from the server and yields it to a block. Replaces
|
27
|
+
# the key's value with the result of the block as long as the key hasn't
|
28
|
+
# been updated in the meantime, otherwise raises
|
29
|
+
# {Couchbase::Error::KeyExists}. CAS stands for "compare and swap", and
|
30
|
+
# avoids the need for manual key mutexing. Read more info here:
|
31
|
+
#
|
32
|
+
# In asynchronous mode it will yield result twice, first for
|
33
|
+
# {Bucket#get} with {Result#operation} equal to +:get+ and
|
34
|
+
# second time for {Bucket#set} with {Result#operation} equal to +:set+.
|
35
|
+
#
|
36
|
+
# @see http://couchbase.com/docs/memcached-api/memcached-api-protocol-text_cas.html
|
37
|
+
#
|
38
|
+
# Setting the +:retry+ option to a positive number will cause this method
|
39
|
+
# to rescue the {Couchbase::Error::KeyExists} error that happens when
|
40
|
+
# an update collision is detected, and automatically get a fresh copy
|
41
|
+
# of the value and retry the block. This will repeat as long as there
|
42
|
+
# continues to be conflicts, up to the maximum number of retries specified.
|
43
|
+
# For asynchronous mode, this means the block will be yielded once for
|
44
|
+
# the initial {Bucket#get}, once for the final {Bucket#set} (successful
|
45
|
+
# or last failure), and zero or more additional {Bucket#get} retries
|
46
|
+
# in between, up to the maximum allowed by the +:retry+ option.
|
47
|
+
#
|
48
|
+
# @param [String, Symbol] key
|
49
|
+
#
|
50
|
+
# @param [Hash] options the options for "swap" part
|
51
|
+
# @option options [Fixnum] :ttl (self.default_ttl) the time to live of this key
|
52
|
+
# @option options [Symbol] :format (self.default_format) format of the value
|
53
|
+
# @option options [Fixnum] :flags (self.default_flags) flags for this key
|
54
|
+
# @option options [Fixnum] :retry (0) maximum number of times to autmatically retry upon update collision
|
55
|
+
#
|
56
|
+
# @yieldparam [Object, Result] value old value in synchronous mode and
|
57
|
+
# +Result+ object in asynchronous mode.
|
58
|
+
# @yieldreturn [Object] new value.
|
59
|
+
#
|
60
|
+
# @raise [Couchbase::Error::KeyExists] if the key was updated before the the
|
61
|
+
# code in block has been completed (the CAS value has been changed).
|
62
|
+
# @raise [ArgumentError] if the block is missing for async mode
|
63
|
+
#
|
64
|
+
# @example Implement append to JSON encoded value
|
65
|
+
#
|
66
|
+
# c.default_format = :document
|
67
|
+
# c.set("foo", {"bar" => 1})
|
68
|
+
# c.cas("foo") do |val|
|
69
|
+
# val["baz"] = 2
|
70
|
+
# val
|
71
|
+
# end
|
72
|
+
# c.get("foo") #=> {"bar" => 1, "baz" => 2}
|
73
|
+
#
|
74
|
+
# @example Append JSON encoded value asynchronously
|
75
|
+
#
|
76
|
+
# c.default_format = :document
|
77
|
+
# c.set("foo", {"bar" => 1})
|
78
|
+
# c.run do
|
79
|
+
# c.cas("foo") do |val|
|
80
|
+
# case val.operation
|
81
|
+
# when :get
|
82
|
+
# val["baz"] = 2
|
83
|
+
# val
|
84
|
+
# when :set
|
85
|
+
# # verify all is ok
|
86
|
+
# puts "error: #{ret.error.inspect}" unless ret.success?
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
# c.get("foo") #=> {"bar" => 1, "baz" => 2}
|
91
|
+
#
|
92
|
+
# @return [Fixnum] the CAS of new value
|
93
|
+
def cas(key, options = {})
|
94
|
+
retries_remaining = options.delete(:retry) || 0
|
95
|
+
if async?
|
96
|
+
block = Proc.new
|
97
|
+
get(key) do |ret|
|
98
|
+
val = block.call(ret) # get new value from caller
|
99
|
+
set(ret.key, val, options.merge(:cas => ret.cas, :flags => ret.flags)) do |set_ret|
|
100
|
+
if set_ret.error.is_a?(Couchbase::Error::KeyExists) && (retries_remaining > 0)
|
101
|
+
cas(key, options.merge(:retry => retries_remaining - 1), &block)
|
102
|
+
else
|
103
|
+
block.call(set_ret)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
else
|
108
|
+
begin
|
109
|
+
val, flags, ver = get(key, :extended => true)
|
110
|
+
val = yield(val) # get new value from caller
|
111
|
+
set(key, val, options.merge(:cas => ver, :flags => flags))
|
112
|
+
rescue Couchbase::Error::KeyExists
|
113
|
+
if retries_remaining > 0
|
114
|
+
retries_remaining -= 1
|
115
|
+
retry
|
116
|
+
else
|
117
|
+
raise
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
alias :compare_and_swap :cas
|
123
|
+
|
124
|
+
# Fetch design docs stored in current bucket
|
125
|
+
#
|
126
|
+
# @since 1.2.0
|
127
|
+
#
|
128
|
+
# @return [Hash]
|
129
|
+
def design_docs
|
130
|
+
req = make_http_request("/pools/default/buckets/#{bucket}/ddocs",
|
131
|
+
:type => :management, :extended => true)
|
132
|
+
docmap = {}
|
133
|
+
req.on_body do |body|
|
134
|
+
res = MultiJson.load(body.value)
|
135
|
+
res["rows"].each do |obj|
|
136
|
+
if obj['doc']
|
137
|
+
obj['doc']['value'] = obj['doc'].delete('json')
|
138
|
+
end
|
139
|
+
doc = DesignDoc.wrap(self, obj)
|
140
|
+
key = doc.id.sub(/^_design\//, '')
|
141
|
+
next if self.environment == :production && key =~ /dev_/
|
142
|
+
docmap[key] = doc
|
143
|
+
end
|
144
|
+
yield(docmap) if block_given?
|
145
|
+
end
|
146
|
+
req.continue
|
147
|
+
async? ? nil : docmap
|
148
|
+
end
|
149
|
+
|
150
|
+
# Update or create design doc with supplied views
|
151
|
+
#
|
152
|
+
# @since 1.2.0
|
153
|
+
#
|
154
|
+
# @param [Hash, IO, String] data The source object containing JSON
|
155
|
+
# encoded design document. It must have +_id+ key set, this key
|
156
|
+
# should start with +_design/+.
|
157
|
+
#
|
158
|
+
# @return [true, false]
|
159
|
+
def save_design_doc(data)
|
160
|
+
attrs = case data
|
161
|
+
when String
|
162
|
+
MultiJson.load(data)
|
163
|
+
when IO
|
164
|
+
MultiJson.load(data.read)
|
165
|
+
when Hash
|
166
|
+
data
|
167
|
+
else
|
168
|
+
raise ArgumentError, "Document should be Hash, String or IO instance"
|
169
|
+
end
|
170
|
+
rv = nil
|
171
|
+
id = attrs.delete('_id').to_s
|
172
|
+
attrs['language'] ||= 'javascript'
|
173
|
+
if id !~ /\A_design\//
|
174
|
+
rv = Result.new(:operation => :http_request,
|
175
|
+
:key => id,
|
176
|
+
:error => ArgumentError.new("'_id' key must be set and start with '_design/'."))
|
177
|
+
yield rv if block_given?
|
178
|
+
raise rv.error unless async?
|
179
|
+
end
|
180
|
+
req = make_http_request(id, :body => MultiJson.dump(attrs),
|
181
|
+
:method => :put, :extended => true)
|
182
|
+
req.on_body do |res|
|
183
|
+
rv = res
|
184
|
+
val = MultiJson.load(res.value)
|
185
|
+
if block_given?
|
186
|
+
if res.success? && val['error']
|
187
|
+
res.error = Error::View.new("save_design_doc", val['error'])
|
188
|
+
end
|
189
|
+
yield(res)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
req.continue
|
193
|
+
unless async?
|
194
|
+
rv.success? or raise res.error
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Delete design doc with given id and revision.
|
199
|
+
#
|
200
|
+
# @since 1.2.0
|
201
|
+
#
|
202
|
+
# @param [String] id Design document id. It might have '_design/'
|
203
|
+
# prefix.
|
204
|
+
#
|
205
|
+
# @param [String] rev Document revision. It uses latest revision if
|
206
|
+
# +rev+ parameter is nil.
|
207
|
+
#
|
208
|
+
# @return [true, false]
|
209
|
+
def delete_design_doc(id, rev = nil)
|
210
|
+
ddoc = design_docs[id.sub(/^_design\//, '')]
|
211
|
+
unless ddoc
|
212
|
+
yield nil if block_given?
|
213
|
+
return nil
|
214
|
+
end
|
215
|
+
path = Utils.build_query(ddoc.id, :rev => rev || ddoc.meta['rev'])
|
216
|
+
req = make_http_request(path, :method => :delete, :extended => true)
|
217
|
+
rv = nil
|
218
|
+
req.on_body do |res|
|
219
|
+
rv = res
|
220
|
+
val = MultiJson.load(res.value)
|
221
|
+
if block_given?
|
222
|
+
if res.success? && val['error']
|
223
|
+
res.error = Error::View.new("delete_design_doc", val['error'])
|
224
|
+
end
|
225
|
+
yield(res)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
req.continue
|
229
|
+
unless async?
|
230
|
+
rv.success? or raise res.error
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Delete contents of the bucket
|
235
|
+
#
|
236
|
+
# @see http://www.couchbase.com/docs/couchbase-manual-2.0/restapi-flushing-bucket.html
|
237
|
+
#
|
238
|
+
# @since 1.2.0.beta
|
239
|
+
#
|
240
|
+
# @yieldparam [Result] ret the object with +error+, +status+ and +operation+
|
241
|
+
# attributes.
|
242
|
+
#
|
243
|
+
# @raise [Couchbase::Error::Protocol] in case of an error is
|
244
|
+
# encountered. Check {Couchbase::Error::Base#status} for detailed code.
|
245
|
+
#
|
246
|
+
# @return [true] always return true (see raise section)
|
247
|
+
#
|
248
|
+
# @example Simple flush the bucket
|
249
|
+
# c.flush #=> true
|
250
|
+
#
|
251
|
+
# @example Asynchronous flush
|
252
|
+
# c.run do
|
253
|
+
# c.flush do |ret|
|
254
|
+
# ret.operation #=> :flush
|
255
|
+
# ret.success? #=> true
|
256
|
+
# ret.status #=> 200
|
257
|
+
# end
|
258
|
+
# end
|
259
|
+
def flush
|
260
|
+
if !async? && block_given?
|
261
|
+
raise ArgumentError, "synchronous mode doesn't support callbacks"
|
262
|
+
end
|
263
|
+
req = make_http_request("/pools/default/buckets/#{bucket}/controller/doFlush",
|
264
|
+
:type => :management, :method => :post, :extended => true)
|
265
|
+
res = nil
|
266
|
+
req.on_body do |r|
|
267
|
+
res = r
|
268
|
+
res.instance_variable_set("@operation", :flush)
|
269
|
+
yield(res) if block_given?
|
270
|
+
end
|
271
|
+
req.continue
|
272
|
+
true
|
273
|
+
end
|
274
|
+
|
275
|
+
# Create and register one-shot timer
|
276
|
+
#
|
277
|
+
# @return [Couchbase::Timer]
|
278
|
+
def create_timer(interval, &block)
|
279
|
+
Timer.new(self, interval, &block)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Create and register periodic timer
|
283
|
+
#
|
284
|
+
# @return [Couchbase::Timer]
|
285
|
+
def create_periodic_timer(interval, &block)
|
286
|
+
Timer.new(self, interval, :periodic => true, &block)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Wait for persistence condition
|
290
|
+
#
|
291
|
+
# @since 1.2.0.dp6
|
292
|
+
#
|
293
|
+
# This operation is useful when some confidence needed regarding the
|
294
|
+
# state of the keys. With two parameters +:replicated+ and +:persisted+
|
295
|
+
# it allows to set up the waiting rule.
|
296
|
+
#
|
297
|
+
# @param [String, Symbol, Array, Hash] keys The list of the keys to
|
298
|
+
# observe. Full form is hash with key-cas value pairs, but there are
|
299
|
+
# also shortcuts like just Array of keys or single key. CAS value
|
300
|
+
# needed to when you need to ensure that the storage persisted exactly
|
301
|
+
# the same version of the key you are asking to observe.
|
302
|
+
# @param [Hash] options The options for operation
|
303
|
+
# @option options [Fixnum] :timeout The timeout in microseconds
|
304
|
+
# @option options [Fixnum] :replicated How many replicas should receive
|
305
|
+
# the copy of the key.
|
306
|
+
# @option options [Fixnum] :persisted How many nodes should store the
|
307
|
+
# key on the disk.
|
308
|
+
#
|
309
|
+
# @raise [Couchbase::Error::Timeout] if the given time is up
|
310
|
+
#
|
311
|
+
# @return [Fixnum, Hash<String, Fixnum>] will return CAS value just like
|
312
|
+
# mutators or pairs key-cas in case of multiple keys.
|
313
|
+
def observe_and_wait(*keys, &block)
|
314
|
+
options = {:timeout => default_observe_timeout}
|
315
|
+
options.update(keys.pop) if keys.size > 1 && keys.last.is_a?(Hash)
|
316
|
+
verify_observe_options(options)
|
317
|
+
if block && !async?
|
318
|
+
raise ArgumentError, "synchronous mode doesn't support callbacks"
|
319
|
+
end
|
320
|
+
if keys.size == 0
|
321
|
+
raise ArgumentError, "at least one key is required"
|
322
|
+
end
|
323
|
+
if keys.size == 1 && keys[0].is_a?(Hash)
|
324
|
+
key_cas = keys[0]
|
325
|
+
else
|
326
|
+
key_cas = keys.flatten.reduce({}) do |h, kk|
|
327
|
+
h[kk] = nil # set CAS to nil
|
328
|
+
h
|
329
|
+
end
|
330
|
+
end
|
331
|
+
if async?
|
332
|
+
do_observe_and_wait(key_cas, options, &block)
|
333
|
+
else
|
334
|
+
res = do_observe_and_wait(key_cas, options, &block) while res.nil?
|
335
|
+
unless async?
|
336
|
+
if keys.size == 1 && (keys[0].is_a?(String) || keys[0].is_a?(Symbol))
|
337
|
+
return res.values.first
|
338
|
+
else
|
339
|
+
return res
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
private
|
346
|
+
|
347
|
+
def verify_observe_options(options)
|
348
|
+
unless num_replicas
|
349
|
+
raise Couchbase::Error::Libcouchbase, "cannot detect number of the replicas"
|
350
|
+
end
|
351
|
+
unless options[:persisted] || options[:replicated]
|
352
|
+
raise ArgumentError, "either :persisted or :replicated option must be set"
|
353
|
+
end
|
354
|
+
if options[:persisted] && !(1..num_replicas + 1).include?(options[:persisted])
|
355
|
+
raise ArgumentError, "persisted number should be in range (1..#{num_replicas + 1})"
|
356
|
+
end
|
357
|
+
if options[:replicated] && !(1..num_replicas).include?(options[:replicated])
|
358
|
+
raise ArgumentError, "replicated number should be in range (1..#{num_replicas})"
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def do_observe_and_wait(keys, options, &block)
|
363
|
+
acc = Hash.new do |h, k|
|
364
|
+
h[k] = Hash.new(0)
|
365
|
+
h[k][:cas] = [keys[k]] # first position is for master node
|
366
|
+
h[k]
|
367
|
+
end
|
368
|
+
check_condition = lambda do
|
369
|
+
ok = catch :break do
|
370
|
+
acc.each do |key, stats|
|
371
|
+
master = stats[:cas][0]
|
372
|
+
if master.nil?
|
373
|
+
# master node doesn't have the key
|
374
|
+
throw :break
|
375
|
+
end
|
376
|
+
if options[:persisted] && (stats[:persisted] < options[:persisted] ||
|
377
|
+
stats[:cas].count(master) != options[:persisted])
|
378
|
+
throw :break
|
379
|
+
end
|
380
|
+
if options[:replicated] && (stats[:replicated] < options[:replicated] ||
|
381
|
+
stats[:cas].count(master) != options[:replicated] + 1)
|
382
|
+
throw :break
|
383
|
+
end
|
384
|
+
end
|
385
|
+
true
|
386
|
+
end
|
387
|
+
if ok
|
388
|
+
if async?
|
389
|
+
options[:timer].cancel if options[:timer]
|
390
|
+
keys.each do |k, _|
|
391
|
+
block.call(Result.new(:key => k,
|
392
|
+
:cas => acc[k][:cas][0],
|
393
|
+
:operation => :observe_and_wait))
|
394
|
+
end
|
395
|
+
return :async
|
396
|
+
else
|
397
|
+
return keys.inject({}){|res, (k, _)| res[k] = acc[k][:cas][0]; res}
|
398
|
+
end
|
399
|
+
else
|
400
|
+
options[:timeout] /= 2
|
401
|
+
if options[:timeout] > 0
|
402
|
+
if async?
|
403
|
+
options[:timer] = create_timer(options[:timeout]) do
|
404
|
+
do_observe_and_wait(keys, options, &block)
|
405
|
+
end
|
406
|
+
return :async
|
407
|
+
else
|
408
|
+
# do wait for timeout
|
409
|
+
run { create_timer(options[:timeout]){} }
|
410
|
+
# return nil to avoid recursive call
|
411
|
+
return nil
|
412
|
+
end
|
413
|
+
else
|
414
|
+
err = Couchbase::Error::Timeout.new("the observe request was timed out")
|
415
|
+
err.instance_variable_set("@operation", :observe_and_wait)
|
416
|
+
if async?
|
417
|
+
keys.each do |k, _|
|
418
|
+
block.call(Result.new(:key => k,
|
419
|
+
:cas => acc[k][:cas][0],
|
420
|
+
:operation => :observe_and_wait,
|
421
|
+
:error => err))
|
422
|
+
end
|
423
|
+
return :async
|
424
|
+
else
|
425
|
+
err.instance_variable_set("@key", keys.keys)
|
426
|
+
raise err
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
collect = lambda do |results|
|
432
|
+
results.each do |res|
|
433
|
+
if res.completed?
|
434
|
+
check_condition.call if async?
|
435
|
+
else
|
436
|
+
if res.from_master?
|
437
|
+
acc[res.key][:cas][0] = res.cas
|
438
|
+
else
|
439
|
+
acc[res.key][:cas] << res.cas
|
440
|
+
end
|
441
|
+
acc[res.key][res.status] += 1
|
442
|
+
if res.status == :persisted
|
443
|
+
acc[res.key][:replicated] += 1
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
if async?
|
449
|
+
observe(keys.keys, options, &collect)
|
450
|
+
else
|
451
|
+
observe(keys.keys, options).each{|_, v| collect.call(v)}
|
452
|
+
check_condition.call
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
end
|