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