couchbase 1.3.3 → 1.3.4
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|