couchbase 1.3.3 → 1.3.4
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 +15 -7
- data/README.markdown +97 -29
- data/RELEASE_NOTES.markdown +26 -0
- data/Rakefile +0 -2
- data/couchbase.gemspec +6 -6
- data/ext/couchbase_ext/arithmetic.c +1 -0
- data/ext/couchbase_ext/bucket.c +1 -1
- data/ext/couchbase_ext/extconf.rb +10 -2
- data/ext/couchbase_ext/multithread_plugin.c +19 -10
- data/lib/active_support/cache/couchbase_store.rb +18 -2
- data/lib/couchbase/bucket.rb +31 -4
- data/lib/couchbase/cluster.rb +16 -2
- data/lib/couchbase/version.rb +1 -1
- data/tasks/compile.rake +63 -25
- data/test/test_arithmetic.rb +9 -0
- data/test/test_cas.rb +158 -0
- data/test/test_couchbase_rails_cache_store.rb +20 -0
- data/test/test_format.rb +6 -1
- metadata +169 -90
@@ -55,6 +55,7 @@ module ActiveSupport
|
|
55
55
|
options[:default_ttl] ||= options.delete(:expires_in)
|
56
56
|
options[:default_format] ||= :marshal
|
57
57
|
options[:key_prefix] ||= options.delete(:namespace)
|
58
|
+
@key_prefix = options[:key_prefix]
|
58
59
|
options[:connection_pool] ||= options.delete(:connection_pool)
|
59
60
|
args.push(options)
|
60
61
|
|
@@ -348,7 +349,7 @@ module ActiveSupport
|
|
348
349
|
# object responds to +cache_key+. Otherwise, to_param method will be
|
349
350
|
# called. If the key is a Hash, then keys will be sorted alphabetically.
|
350
351
|
def expanded_key(key) # :nodoc:
|
351
|
-
return key.cache_key.to_s if key.respond_to?(:cache_key)
|
352
|
+
return validate_key(key.cache_key.to_s) if key.respond_to?(:cache_key)
|
352
353
|
|
353
354
|
case key
|
354
355
|
when Array
|
@@ -361,7 +362,22 @@ module ActiveSupport
|
|
361
362
|
key = key.sort_by { |k,_| k.to_s }.collect{|k,v| "#{k}=#{v}"}
|
362
363
|
end
|
363
364
|
|
364
|
-
key.respond_to?(:to_param) ? key.to_param : key
|
365
|
+
validate_key(key.respond_to?(:to_param) ? key.to_param : key)
|
366
|
+
end
|
367
|
+
|
368
|
+
def validate_key(key)
|
369
|
+
if key_with_prefix(key).length > 250
|
370
|
+
key = "#{key[0, max_length_before_prefix]}:md5:#{Digest::MD5.hexdigest(key)}"
|
371
|
+
end
|
372
|
+
return key
|
373
|
+
end
|
374
|
+
|
375
|
+
def key_with_prefix(key)
|
376
|
+
(ns = @key_prefix) ? "#{ns}#{key}" : key
|
377
|
+
end
|
378
|
+
|
379
|
+
def max_length_before_prefix
|
380
|
+
@max_length_before_prefix ||= 212 - (@key_prefix || '').size
|
365
381
|
end
|
366
382
|
|
367
383
|
module Threadsafe
|
data/lib/couchbase/bucket.rb
CHANGED
@@ -35,12 +35,23 @@ module Couchbase
|
|
35
35
|
#
|
36
36
|
# @see http://couchbase.com/docs/memcached-api/memcached-api-protocol-text_cas.html
|
37
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
|
+
#
|
38
48
|
# @param [String, Symbol] key
|
39
49
|
#
|
40
50
|
# @param [Hash] options the options for "swap" part
|
41
51
|
# @option options [Fixnum] :ttl (self.default_ttl) the time to live of this key
|
42
52
|
# @option options [Symbol] :format (self.default_format) format of the value
|
43
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
|
44
55
|
#
|
45
56
|
# @yieldparam [Object, Result] value old value in synchronous mode and
|
46
57
|
# +Result+ object in asynchronous mode.
|
@@ -80,16 +91,32 @@ module Couchbase
|
|
80
91
|
#
|
81
92
|
# @return [Fixnum] the CAS of new value
|
82
93
|
def cas(key, options = {})
|
94
|
+
retries_remaining = options.delete(:retry) || 0
|
83
95
|
if async?
|
84
96
|
block = Proc.new
|
85
97
|
get(key) do |ret|
|
86
98
|
val = block.call(ret) # get new value from caller
|
87
|
-
set(ret.key, val, options.merge(:cas => ret.cas, :flags => ret.flags)
|
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
|
88
106
|
end
|
89
107
|
else
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
93
120
|
end
|
94
121
|
end
|
95
122
|
alias :compare_and_swap :cas
|
data/lib/couchbase/cluster.rb
CHANGED
@@ -42,11 +42,19 @@ module Couchbase
|
|
42
42
|
# bucket. Possible values are "memcached" and "couchbase".
|
43
43
|
# @option options [Fixnum] :ram_quota (100) The RAM quota in megabytes.
|
44
44
|
# @option options [Fixnum] :replica_number (1) The number of replicas of
|
45
|
-
# each document
|
45
|
+
# each document. Minimum 0, maximum 3.
|
46
46
|
# @option options [String] :auth_type ("sasl") The authentication type.
|
47
47
|
# Possible values are "sasl" and "none". Note you should specify free
|
48
48
|
# port for "none"
|
49
49
|
# @option options [Fixnum] :proxy_port The port for moxi
|
50
|
+
# @option options [true, false] :replica_index (true) Disable or
|
51
|
+
# enable indexes for bucket replicas
|
52
|
+
# @option options [true, false] :flush_enabled (false) Enables the
|
53
|
+
# 'flush all' functionality on the specified bucket.
|
54
|
+
# @option options [true, false] :parallel_db_and_view_compaction (false)
|
55
|
+
# Indicates whether database and view files on disk can be
|
56
|
+
# compacted simultaneously
|
57
|
+
#
|
50
58
|
def create_bucket(name, options = {})
|
51
59
|
defaults = {
|
52
60
|
:type => "couchbase",
|
@@ -54,7 +62,10 @@ module Couchbase
|
|
54
62
|
:replica_number => 1,
|
55
63
|
:auth_type => "sasl",
|
56
64
|
:sasl_password => "",
|
57
|
-
:proxy_port => nil
|
65
|
+
:proxy_port => nil,
|
66
|
+
:flush_enabled => false,
|
67
|
+
:replica_index => true,
|
68
|
+
:parallel_db_and_view_compaction => false
|
58
69
|
}
|
59
70
|
options = defaults.merge(options)
|
60
71
|
params = {"name" => name}
|
@@ -64,6 +75,9 @@ module Couchbase
|
|
64
75
|
params["authType"] = options[:auth_type]
|
65
76
|
params["saslPassword"] = options[:sasl_password]
|
66
77
|
params["proxyPort"] = options[:proxy_port]
|
78
|
+
params["flushEnabled"] = !!options[:flush_enabled]
|
79
|
+
params["replicaIndex"] = !!options[:replica_index]
|
80
|
+
params["parallelDBAndViewCompaction"] = !!options[:parallel_db_and_view_compaction]
|
67
81
|
payload = Utils.encode_params(params.reject!{|k, v| v.nil?})
|
68
82
|
request = @connection.make_http_request("/pools/default/buckets",
|
69
83
|
:content_type => "application/x-www-form-urlencoded",
|
data/lib/couchbase/version.rb
CHANGED
data/tasks/compile.rake
CHANGED
@@ -30,6 +30,36 @@ version_router = lambda do |t|
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
class Platform
|
34
|
+
attr_reader :name, :host, :versions
|
35
|
+
|
36
|
+
def initialize(params)
|
37
|
+
@name = params[:name]
|
38
|
+
@host = params[:host]
|
39
|
+
@versions = params[:versions]
|
40
|
+
end
|
41
|
+
|
42
|
+
def each_version
|
43
|
+
@versions.each do |v|
|
44
|
+
yield(v, v[/\d\.\d\.\d/])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def short_versions
|
49
|
+
res = []
|
50
|
+
each_version do |long, short|
|
51
|
+
res << short
|
52
|
+
end
|
53
|
+
res
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
recent = "2.0.0-p353"
|
58
|
+
CROSS_PLATFORMS = [
|
59
|
+
Platform.new(:name => 'x64-mingw32', :host => 'x86_64-w64-mingw32', :versions => %w(1.9.3-p484 2.0.0-p353 2.1.0)),
|
60
|
+
Platform.new(:name => 'x86-mingw32', :host => 'i686-w64-mingw32', :versions => %w(1.8.7-p374 1.9.3-p484 2.0.0-p353 2.1.0)),
|
61
|
+
]
|
62
|
+
|
33
63
|
# Setup compile tasks. Configuration can be passed via ENV.
|
34
64
|
# Example:
|
35
65
|
# rake compile with_libcouchbase_include=/opt/couchbase/include
|
@@ -41,13 +71,14 @@ end
|
|
41
71
|
#
|
42
72
|
Rake::ExtensionTask.new("couchbase_ext", gemspec) do |ext|
|
43
73
|
ext.cross_compile = true
|
44
|
-
ext.cross_platform = ENV['
|
74
|
+
ext.cross_platform = ENV['TARGET']
|
45
75
|
if ENV['RUBY_CC_VERSION']
|
46
76
|
ext.lib_dir = "lib/couchbase"
|
47
77
|
end
|
48
78
|
ext.cross_compiling do |spec|
|
49
79
|
spec.files.delete("lib/couchbase/couchbase_ext.so")
|
50
|
-
spec.files.push("lib/couchbase_ext.rb", Dir["lib/couchbase
|
80
|
+
spec.files.push("lib/couchbase_ext.rb", Dir["lib/couchbase/*/couchbase_ext.so"])
|
81
|
+
spec.files.push(Dir["lib/couchbase/*/couchbase_ext.so"])
|
51
82
|
file "#{ext.tmp_dir}/#{ext.cross_platform}/stage/lib/couchbase_ext.rb", &version_router
|
52
83
|
end
|
53
84
|
|
@@ -66,19 +97,13 @@ end
|
|
66
97
|
|
67
98
|
require 'rubygems/package_task'
|
68
99
|
Gem::PackageTask.new(gemspec) do |pkg|
|
69
|
-
pkg.need_tar =
|
100
|
+
pkg.need_tar = pkg.need_zip = false
|
70
101
|
end
|
71
102
|
|
72
103
|
require 'mini_portile'
|
73
104
|
require 'rake/extensioncompiler'
|
74
105
|
|
75
106
|
class MiniPortile
|
76
|
-
alias :initialize_with_default_host :initialize
|
77
|
-
def initialize(name, version)
|
78
|
-
initialize_with_default_host(name, version)
|
79
|
-
@host = ENV['HOST'] || Rake::ExtensionCompiler.mingw_host
|
80
|
-
end
|
81
|
-
|
82
107
|
alias :cook_without_checkpoint :cook
|
83
108
|
def cook
|
84
109
|
checkpoint = "ports/.#{name}-#{version}-#{host}.installed"
|
@@ -89,13 +114,30 @@ class MiniPortile
|
|
89
114
|
end
|
90
115
|
end
|
91
116
|
|
92
|
-
|
93
|
-
directory "ports"
|
117
|
+
file "lib/couchbase_ext.rb", &version_router
|
94
118
|
|
95
|
-
|
96
|
-
|
119
|
+
desc "Package gem for windows"
|
120
|
+
task "package:windows" => ["package", "lib/couchbase_ext.rb"] do
|
121
|
+
vars = [
|
122
|
+
'CC',
|
123
|
+
'CFLAGS',
|
124
|
+
'CPATH',
|
125
|
+
'CPP',
|
126
|
+
'CPPFLAGS',
|
127
|
+
'LDFLAGS',
|
128
|
+
'LIBRARY_PATH',
|
129
|
+
'PATH'
|
130
|
+
].reduce({}) do |h, v|
|
131
|
+
h[v] = ENV[v]
|
132
|
+
h
|
133
|
+
end
|
134
|
+
CROSS_PLATFORMS.each do |platform|
|
135
|
+
ENV['TARGET'] = platform.name
|
136
|
+
rm_rf("tmp/ ports/")
|
137
|
+
mkdir_p("ports")
|
138
|
+
recipe = MiniPortile.new("libcouchbase", "2.2.0_30_gc87bec4")
|
139
|
+
recipe.host = platform.host
|
97
140
|
recipe.files << "http://packages.couchbase.com/clients/c/libcouchbase-#{recipe.version}.tar.gz"
|
98
|
-
|
99
141
|
recipe.configure_options.push("--disable-debug",
|
100
142
|
"--disable-dependency-tracking",
|
101
143
|
"--disable-couchbasemock",
|
@@ -105,16 +147,12 @@ namespace :ports do
|
|
105
147
|
"--disable-tools")
|
106
148
|
recipe.cook
|
107
149
|
recipe.activate
|
150
|
+
platform.each_version do |long, short|
|
151
|
+
sh("env RUBY_CC_VERSION=#{short} RBENV_VERSION=#{long} rbenv exec rake cross compile")
|
152
|
+
end
|
153
|
+
vars.each do |k, v|
|
154
|
+
ENV[k] = v
|
155
|
+
end
|
156
|
+
sh("env RUBY_CC_VERSION=#{platform.short_versions.join(":")} RBENV_VERSION=#{recent} rbenv exec rake cross native gem")
|
108
157
|
end
|
109
158
|
end
|
110
|
-
|
111
|
-
file "lib/couchbase_ext.rb", &version_router
|
112
|
-
task :cross => ["lib/couchbase_ext.rb", "ports:libcouchbase"]
|
113
|
-
|
114
|
-
desc "Package gem for windows"
|
115
|
-
task "package:windows" => :package do
|
116
|
-
sh("env RUBY_CC_VERSION=1.8.7 RBENV_VERSION=1.8.7-p370 rbenv exec bundle exec rake cross compile")
|
117
|
-
sh("env RUBY_CC_VERSION=1.9.2 RBENV_VERSION=1.9.2-p320 rbenv exec bundle exec rake cross compile")
|
118
|
-
sh("env RUBY_CC_VERSION=2.0.0 RBENV_VERSION=2.0.0-p247 rbenv exec bundle exec rake cross compile")
|
119
|
-
sh("env RUBY_CC_VERSION=1.8.7:1.9.2:2.0.0 RBENV_VERSION=1.9.2-p320 rbenv exec bundle exec rake cross native gem")
|
120
|
-
end
|
data/test/test_arithmetic.rb
CHANGED
@@ -173,4 +173,13 @@ class TestArithmetic < MiniTest::Test
|
|
173
173
|
assert_equal [2, 2], connection.decr(uniq_id(:foo), uniq_id(:bar), :delta => 10).values.sort
|
174
174
|
assert_equal [1, 1], connection.decr(uniq_id(:foo), uniq_id(:bar)).values.sort
|
175
175
|
end
|
176
|
+
|
177
|
+
def test_it_returns_cas_value_in_extended_mode
|
178
|
+
connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
|
179
|
+
orig_cas = connection.set(uniq_id(:foo), 1)
|
180
|
+
val, cas = connection.incr(uniq_id(:foo), :extended => true)
|
181
|
+
assert_equal 2, val
|
182
|
+
assert cas.is_a?(Numeric), "CAS should be numeric value: #{cas.inspect}"
|
183
|
+
refute_equal orig_cas, cas
|
184
|
+
end
|
176
185
|
end
|
data/test/test_cas.rb
CHANGED
@@ -40,6 +40,69 @@ class TestCas < MiniTest::Test
|
|
40
40
|
assert_equal expected, val
|
41
41
|
end
|
42
42
|
|
43
|
+
def test_compare_and_swap_collision
|
44
|
+
connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port,
|
45
|
+
:default_format => :document)
|
46
|
+
connection.set(uniq_id, {"bar" => 1})
|
47
|
+
assert_raises(Couchbase::Error::KeyExists) do
|
48
|
+
connection.cas(uniq_id) do |val|
|
49
|
+
# Simulate collision with a separate writer. This will
|
50
|
+
# change the CAS value to be different than what #cas just loaded.
|
51
|
+
connection.set(uniq_id, {"bar" => 2})
|
52
|
+
|
53
|
+
# Complete the modification we desire, which should fail when set.
|
54
|
+
val["baz"] = 3
|
55
|
+
val
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_compare_and_swap_retry
|
61
|
+
connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port,
|
62
|
+
:default_format => :document)
|
63
|
+
connection.set(uniq_id, {"bar" => 1})
|
64
|
+
calls = 0
|
65
|
+
connection.cas(uniq_id, :retry => 1) do |val|
|
66
|
+
calls += 1
|
67
|
+
if calls == 1
|
68
|
+
# Simulate collision with a separate writer. This will
|
69
|
+
# change the CAS value to be different than what #cas just loaded.
|
70
|
+
# Only do this the first time this block is executed.
|
71
|
+
connection.set(uniq_id, {"bar" => 2})
|
72
|
+
end
|
73
|
+
|
74
|
+
# Complete the modification we desire, which should fail when set.
|
75
|
+
val["baz"] = 3
|
76
|
+
val
|
77
|
+
end
|
78
|
+
assert_equal 2, calls
|
79
|
+
val = connection.get(uniq_id)
|
80
|
+
expected = {"bar" => 2, "baz" => 3}
|
81
|
+
assert_equal expected, val
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_compare_and_swap_too_many_retries
|
85
|
+
connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port,
|
86
|
+
:default_format => :document)
|
87
|
+
connection.set(uniq_id, {"bar" => 0})
|
88
|
+
calls = 0
|
89
|
+
assert_raises(Couchbase::Error::KeyExists) do
|
90
|
+
connection.cas(uniq_id, :retry => 10) do |val|
|
91
|
+
calls += 1
|
92
|
+
|
93
|
+
# Simulate collision with a separate writer. This will
|
94
|
+
# change the CAS value to be different than what #cas just loaded.
|
95
|
+
# Do it every time so we just keep retrying and failing.
|
96
|
+
connection.set(uniq_id, {"bar" => calls})
|
97
|
+
|
98
|
+
# Complete the modification we desire, which should fail when set.
|
99
|
+
val["baz"] = 3
|
100
|
+
val
|
101
|
+
end
|
102
|
+
end
|
103
|
+
assert_equal 11, calls
|
104
|
+
end
|
105
|
+
|
43
106
|
def test_compare_and_swap_async
|
44
107
|
connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port,
|
45
108
|
:default_format => :document)
|
@@ -66,6 +129,101 @@ class TestCas < MiniTest::Test
|
|
66
129
|
assert_equal expected, val
|
67
130
|
end
|
68
131
|
|
132
|
+
def test_compare_and_swap_async_collision
|
133
|
+
connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port,
|
134
|
+
:default_format => :document)
|
135
|
+
connection.set(uniq_id, {"bar" => 1})
|
136
|
+
calls = 0
|
137
|
+
connection.run do |conn|
|
138
|
+
conn.cas(uniq_id) do |ret|
|
139
|
+
calls += 1
|
140
|
+
case ret.operation
|
141
|
+
when :get
|
142
|
+
new_val = ret.value
|
143
|
+
|
144
|
+
# Simulate collision with a separate writer. This will
|
145
|
+
# change the CAS value to be different than what #cas just loaded.
|
146
|
+
connection.set(uniq_id, {"bar" => 2})
|
147
|
+
|
148
|
+
|
149
|
+
# Complete the modification we desire, which should fail when set.
|
150
|
+
new_val["baz"] = 3
|
151
|
+
new_val
|
152
|
+
when :set
|
153
|
+
assert ret.error.is_a? Couchbase::Error::KeyExists
|
154
|
+
else
|
155
|
+
flunk "Unexpected operation: #{ret.operation.inspect}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
assert_equal 2, calls
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_compare_and_swap_async_retry
|
163
|
+
connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port,
|
164
|
+
:default_format => :document)
|
165
|
+
connection.set(uniq_id, {"bar" => 1})
|
166
|
+
calls = 0
|
167
|
+
connection.run do |conn|
|
168
|
+
conn.cas(uniq_id, :retry => 1) do |ret|
|
169
|
+
calls += 1
|
170
|
+
case ret.operation
|
171
|
+
when :get
|
172
|
+
new_val = ret.value
|
173
|
+
|
174
|
+
if calls == 1
|
175
|
+
# Simulate collision with a separate writer. This will
|
176
|
+
# change the CAS value to be different than what #cas just loaded.
|
177
|
+
# Only do this the first time this block is executed.
|
178
|
+
connection.set(uniq_id, {"bar" => 2})
|
179
|
+
end
|
180
|
+
|
181
|
+
# Complete the modification we desire, which should fail when set.
|
182
|
+
new_val["baz"] = 3
|
183
|
+
new_val
|
184
|
+
when :set
|
185
|
+
assert ret.success?
|
186
|
+
else
|
187
|
+
flunk "Unexpected operation: #{ret.operation.inspect}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
assert_equal 3, calls
|
192
|
+
val = connection.get(uniq_id)
|
193
|
+
expected = {"bar" => 2, "baz" => 3}
|
194
|
+
assert_equal expected, val
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_compare_and_swap_async_too_many_retries
|
198
|
+
connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port,
|
199
|
+
:default_format => :document)
|
200
|
+
connection.set(uniq_id, {"bar" => 0})
|
201
|
+
calls = 0
|
202
|
+
connection.run do |conn|
|
203
|
+
conn.cas(uniq_id, :retry => 10) do |ret|
|
204
|
+
calls += 1
|
205
|
+
case ret.operation
|
206
|
+
when :get
|
207
|
+
new_val = ret.value
|
208
|
+
|
209
|
+
# Simulate collision with a separate writer. This will
|
210
|
+
# change the CAS value to be different than what #cas just loaded.
|
211
|
+
# Do it every time so we just keep retrying and failing.
|
212
|
+
connection.set(uniq_id, {"bar" => calls})
|
213
|
+
|
214
|
+
# Complete the modification we desire, which should fail when set.
|
215
|
+
new_val["baz"] = 3
|
216
|
+
new_val
|
217
|
+
when :set
|
218
|
+
assert ret.error.is_a? Couchbase::Error::KeyExists
|
219
|
+
else
|
220
|
+
flunk "Unexpected operation: #{ret.operation.inspect}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
assert_equal 12, calls
|
225
|
+
end
|
226
|
+
|
69
227
|
def test_flags_replication
|
70
228
|
connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port,
|
71
229
|
:default_format => :document)
|