Skip to content

Commit 708d2ac

Browse files
committed
Add DocumentNotLocked error & C++ core update
1 parent 2a19f12 commit 708d2ac

File tree

7 files changed

+69
-29
lines changed

7 files changed

+69
-29
lines changed

ext/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ target_link_libraries(
5151
PRIVATE project_options
5252
project_warnings
5353
couchbase_cxx_client
54+
Microsoft.GSL::GSL
55+
asio
56+
taocpp::json
5457
snappy)
5558
if(RUBY_LIBRUBY)
5659
target_link_directories(couchbase PRIVATE "${RUBY_LIBRARY_DIR}")

ext/couchbase

ext/couchbase.cxx

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <core/meta/version.hxx>
2222

2323
#include <asio.hpp>
24+
#include <openssl/crypto.h>
2425
#include <spdlog/cfg/env.h>
2526
#include <spdlog/sinks/base_sink.h>
2627
#include <spdlog/spdlog.h>
@@ -37,6 +38,7 @@
3738
#include <core/agent_group.hxx>
3839
#include <core/design_document_namespace_fmt.hxx>
3940
#include <core/logger/configuration.hxx>
41+
#include <core/logger/logger.hxx>
4042
#include <core/operations.hxx>
4143

4244
#include <core/operations/management/analytics.hxx>
@@ -485,7 +487,7 @@ cb_Backend_allocate(VALUE klass)
485487
cb_backend_data* backend = nullptr;
486488
VALUE obj = TypedData_Make_Struct(klass, cb_backend_data, &cb_backend_type, backend);
487489
backend->ctx = std::make_unique<asio::io_context>();
488-
backend->cluster = couchbase::core::cluster::create(*backend->ctx);
490+
backend->cluster = std::make_shared<couchbase::core::cluster>(*backend->ctx);
489491
backend->worker = std::thread([backend]() { backend->ctx->run(); });
490492
return obj;
491493
}
@@ -527,6 +529,7 @@ static VALUE eDocumentExists;
527529
static VALUE eDocumentIrretrievable;
528530
static VALUE eDocumentLocked;
529531
static VALUE eDocumentNotFound;
532+
static VALUE eDocumentNotLocked;
530533
static VALUE eDocumentNotJson;
531534
static VALUE eDurabilityAmbiguous;
532535
static VALUE eDurabilityImpossible;
@@ -623,6 +626,7 @@ init_exceptions(VALUE mCouchbase)
623626
eDocumentIrretrievable = rb_define_class_under(mError, "DocumentIrretrievable", eCouchbaseError);
624627
eDocumentLocked = rb_define_class_under(mError, "DocumentLocked", eCouchbaseError);
625628
eDocumentNotFound = rb_define_class_under(mError, "DocumentNotFound", eCouchbaseError);
629+
eDocumentNotLocked = rb_define_class_under(mError, "DocumentNotLocked", eCouchbaseError);
626630
eDocumentNotJson = rb_define_class_under(mError, "DocumentNotJson", eCouchbaseError);
627631
eDurabilityAmbiguous = rb_define_class_under(mError, "DurabilityAmbiguous", eCouchbaseError);
628632
eDurabilityImpossible = rb_define_class_under(mError, "DurabilityImpossible", eCouchbaseError);
@@ -776,6 +780,9 @@ cb_map_error_code(std::error_code ec, const std::string& message, bool include_e
776780
case couchbase::errc::key_value::document_locked:
777781
return rb_exc_new_cstr(eDocumentLocked, what.c_str());
778782

783+
case couchbase::errc::key_value::document_not_locked:
784+
return rb_exc_new_cstr(eDocumentNotLocked, what.c_str());
785+
779786
case couchbase::errc::key_value::value_too_large:
780787
return rb_exc_new_cstr(eValueTooLarge, what.c_str());
781788

@@ -1802,6 +1809,12 @@ cb_extract_timeout(std::chrono::milliseconds& field, VALUE options)
18021809
cb_extract_duration(field, options, "timeout");
18031810
}
18041811

1812+
static void
1813+
cb_extract_timeout(std::optional<std::chrono::milliseconds>& field, VALUE options)
1814+
{
1815+
cb_extract_duration(field, options, "timeout");
1816+
}
1817+
18051818
static void
18061819
cb_extract_cas(couchbase::cas& field, VALUE cas)
18071820
{
@@ -2454,9 +2467,12 @@ cb_Backend_ping(VALUE self, VALUE bucket, VALUE options)
24542467
}
24552468
}
24562469
}
2470+
std::optional<std::chrono::milliseconds> timeout{};
2471+
cb_extract_timeout(timeout, options);
2472+
24572473
auto barrier = std::make_shared<std::promise<couchbase::core::diag::ping_result>>();
24582474
auto f = barrier->get_future();
2459-
cluster->ping(report_id, bucket_name, selected_services, [barrier](couchbase::core::diag::ping_result&& resp) {
2475+
cluster->ping(report_id, bucket_name, selected_services, timeout, [barrier](couchbase::core::diag::ping_result&& resp) {
24602476
barrier->set_value(std::move(resp));
24612477
});
24622478
auto resp = cb_wait_for_future(f);
@@ -2583,7 +2599,7 @@ cb_Backend_document_get_any_replica(VALUE self, VALUE bucket, VALUE scope, VALUE
25832599
couchbase::get_any_replica_options opts;
25842600
couchbase::ruby::set_timeout(opts, options);
25852601

2586-
auto f = couchbase::cluster(core)
2602+
auto f = couchbase::cluster(*core)
25872603
.bucket(cb_string_new(bucket))
25882604
.scope(cb_string_new(scope))
25892605
.collection(cb_string_new(collection))
@@ -2621,7 +2637,7 @@ cb_Backend_document_get_all_replicas(VALUE self, VALUE bucket, VALUE scope, VALU
26212637
couchbase::get_all_replicas_options opts;
26222638
couchbase::ruby::set_timeout(opts, options);
26232639

2624-
auto f = couchbase::cluster(core)
2640+
auto f = couchbase::cluster(*core)
26252641
.bucket(cb_string_new(bucket))
26262642
.scope(cb_string_new(scope))
26272643
.collection(cb_string_new(collection))
@@ -3018,12 +3034,11 @@ cb_Backend_document_upsert(VALUE self, VALUE bucket, VALUE scope, VALUE collecti
30183034
couchbase::ruby::set_durability(opts, options);
30193035
couchbase::ruby::set_preserve_expiry(opts, options);
30203036

3021-
auto f = couchbase::cluster(core)
3037+
auto f = couchbase::cluster(*core)
30223038
.bucket(cb_string_new(bucket))
30233039
.scope(cb_string_new(scope))
30243040
.collection(cb_string_new(collection))
3025-
.upsert<couchbase::ruby::passthrough_transcoder>(
3026-
cb_string_new(id), couchbase::codec::encoded_value{ cb_binary_new(content), FIX2UINT(flags) }, opts);
3041+
.upsert(cb_string_new(id), couchbase::codec::encoded_value{ cb_binary_new(content), FIX2UINT(flags) }, opts);
30273042

30283043
auto [ctx, resp] = cb_wait_for_future(f);
30293044
if (ctx.ec()) {
@@ -3051,7 +3066,7 @@ cb_Backend_document_upsert_multi(VALUE self, VALUE bucket, VALUE scope, VALUE co
30513066
couchbase::ruby::set_durability(opts, options);
30523067
couchbase::ruby::set_preserve_expiry(opts, options);
30533068

3054-
auto c = couchbase::cluster(core).bucket(cb_string_new(bucket)).scope(cb_string_new(scope)).collection(cb_string_new(collection));
3069+
auto c = couchbase::cluster(*core).bucket(cb_string_new(bucket)).scope(cb_string_new(scope)).collection(cb_string_new(collection));
30553070

30563071
std::vector<std::pair<std::string, couchbase::codec::encoded_value>> tuples{};
30573072
cb_extract_array_of_id_content(tuples, id_content);
@@ -3061,7 +3076,7 @@ cb_Backend_document_upsert_multi(VALUE self, VALUE bucket, VALUE scope, VALUE co
30613076
futures.reserve(num_of_tuples);
30623077

30633078
for (auto& [id, content] : tuples) {
3064-
futures.emplace_back(c.upsert<couchbase::ruby::passthrough_transcoder>(std::move(id), content, opts));
3079+
futures.emplace_back(c.upsert(std::move(id), content, opts));
30653080
}
30663081

30673082
VALUE res = rb_ary_new_capa(static_cast<long>(num_of_tuples));
@@ -3102,7 +3117,7 @@ cb_Backend_document_append(VALUE self, VALUE bucket, VALUE scope, VALUE collecti
31023117
couchbase::ruby::set_timeout(opts, options);
31033118
couchbase::ruby::set_durability(opts, options);
31043119

3105-
auto f = couchbase::cluster(core)
3120+
auto f = couchbase::cluster(*core)
31063121
.bucket(cb_string_new(bucket))
31073122
.scope(cb_string_new(scope))
31083123
.collection(cb_string_new(collection))
@@ -3142,7 +3157,7 @@ cb_Backend_document_prepend(VALUE self, VALUE bucket, VALUE scope, VALUE collect
31423157
couchbase::ruby::set_timeout(opts, options);
31433158
couchbase::ruby::set_durability(opts, options);
31443159

3145-
auto f = couchbase::cluster(core)
3160+
auto f = couchbase::cluster(*core)
31463161
.bucket(cb_string_new(bucket))
31473162
.scope(cb_string_new(scope))
31483163
.collection(cb_string_new(collection))
@@ -3186,12 +3201,11 @@ cb_Backend_document_replace(VALUE self, VALUE bucket, VALUE scope, VALUE collect
31863201
couchbase::ruby::set_preserve_expiry(opts, options);
31873202
couchbase::ruby::set_cas(opts, options);
31883203

3189-
auto f = couchbase::cluster(core)
3204+
auto f = couchbase::cluster(*core)
31903205
.bucket(cb_string_new(bucket))
31913206
.scope(cb_string_new(scope))
31923207
.collection(cb_string_new(collection))
3193-
.replace<couchbase::ruby::passthrough_transcoder>(
3194-
cb_string_new(id), couchbase::codec::encoded_value{ cb_binary_new(content), FIX2UINT(flags) }, opts);
3208+
.replace(cb_string_new(id), couchbase::codec::encoded_value{ cb_binary_new(content), FIX2UINT(flags) }, opts);
31953209

31963210
auto [ctx, resp] = cb_wait_for_future(f);
31973211
if (ctx.ec()) {
@@ -3228,12 +3242,11 @@ cb_Backend_document_insert(VALUE self, VALUE bucket, VALUE scope, VALUE collecti
32283242
couchbase::ruby::set_expiry(opts, options);
32293243
couchbase::ruby::set_durability(opts, options);
32303244

3231-
auto f = couchbase::cluster(core)
3245+
auto f = couchbase::cluster(*core)
32323246
.bucket(cb_string_new(bucket))
32333247
.scope(cb_string_new(scope))
32343248
.collection(cb_string_new(collection))
3235-
.insert<couchbase::ruby::passthrough_transcoder>(
3236-
cb_string_new(id), couchbase::codec::encoded_value{ cb_binary_new(content), FIX2UINT(flags) }, opts);
3249+
.insert(cb_string_new(id), couchbase::codec::encoded_value{ cb_binary_new(content), FIX2UINT(flags) }, opts);
32373250

32383251
auto [ctx, resp] = cb_wait_for_future(f);
32393252
if (ctx.ec()) {
@@ -3268,7 +3281,7 @@ cb_Backend_document_remove(VALUE self, VALUE bucket, VALUE scope, VALUE collecti
32683281
couchbase::ruby::set_durability(opts, options);
32693282
couchbase::ruby::set_cas(opts, options);
32703283

3271-
auto f = couchbase::cluster(core)
3284+
auto f = couchbase::cluster(*core)
32723285
.bucket(cb_string_new(bucket))
32733286
.scope(cb_string_new(scope))
32743287
.collection(cb_string_new(collection))
@@ -3305,7 +3318,7 @@ cb_Backend_document_remove_multi(VALUE self, VALUE bucket, VALUE scope, VALUE co
33053318
std::vector<std::pair<std::string, couchbase::cas>> tuples{};
33063319
cb_extract_array_of_id_cas(tuples, id_cas);
33073320

3308-
auto c = couchbase::cluster(core).bucket(cb_string_new(bucket)).scope(cb_string_new(scope)).collection(cb_string_new(collection));
3321+
auto c = couchbase::cluster(*core).bucket(cb_string_new(bucket)).scope(cb_string_new(scope)).collection(cb_string_new(collection));
33093322

33103323
auto num_of_tuples = tuples.size();
33113324
std::vector<std::future<std::pair<couchbase::key_value_error_context, couchbase::mutation_result>>> futures;
@@ -3357,7 +3370,7 @@ cb_Backend_document_increment(VALUE self, VALUE bucket, VALUE scope, VALUE colle
33573370
couchbase::ruby::set_delta(opts, options);
33583371
couchbase::ruby::set_initial_value(opts, options);
33593372

3360-
auto f = couchbase::cluster(core)
3373+
auto f = couchbase::cluster(*core)
33613374
.bucket(cb_string_new(bucket))
33623375
.scope(cb_string_new(scope))
33633376
.collection(cb_string_new(collection))
@@ -3401,7 +3414,7 @@ cb_Backend_document_decrement(VALUE self, VALUE bucket, VALUE scope, VALUE colle
34013414
couchbase::ruby::set_delta(opts, options);
34023415
couchbase::ruby::set_initial_value(opts, options);
34033416

3404-
auto f = couchbase::cluster(core)
3417+
auto f = couchbase::cluster(*core)
34053418
.bucket(cb_string_new(bucket))
34063419
.scope(cb_string_new(scope))
34073420
.collection(cb_string_new(collection))
@@ -3873,7 +3886,7 @@ cb_Backend_document_mutate_in(VALUE self, VALUE bucket, VALUE scope, VALUE colle
38733886
}
38743887
}
38753888

3876-
auto f = couchbase::cluster(core)
3889+
auto f = couchbase::cluster(*core)
38773890
.bucket(cb_string_new(bucket))
38783891
.scope(cb_string_new(scope))
38793892
.collection(cb_string_new(collection))
@@ -4095,7 +4108,7 @@ cb_Backend_document_scan_create(VALUE self, VALUE bucket, VALUE scope, VALUE col
40954108
auto collection_name = cb_string_new(collection);
40964109

40974110
// Getting the operation agent
4098-
auto agent_group = couchbase::core::agent_group(cluster->io_context(), couchbase::core::agent_group_config{ { cluster } });
4111+
auto agent_group = couchbase::core::agent_group(cluster->io_context(), couchbase::core::agent_group_config{ { *cluster } });
40994112
agent_group.open_bucket(bucket_name);
41004113
auto agent = agent_group.get_agent(bucket_name);
41014114
if (!agent.has_value()) {
@@ -6939,11 +6952,11 @@ cb_Backend_search_get_stats(VALUE self, VALUE timeout)
69396952
const auto& cluster = cb_backend_to_cluster(self);
69406953

69416954
try {
6942-
couchbase::core::operations::management::search_index_stats_request req{};
6955+
couchbase::core::operations::management::search_get_stats_request req{};
69436956
cb_extract_timeout(req, timeout);
6944-
auto barrier = std::make_shared<std::promise<couchbase::core::operations::management::search_index_stats_response>>();
6957+
auto barrier = std::make_shared<std::promise<couchbase::core::operations::management::search_get_stats_response>>();
69456958
auto f = barrier->get_future();
6946-
cluster->execute(req, [barrier](couchbase::core::operations::management::search_index_stats_response&& resp) {
6959+
cluster->execute(req, [barrier](couchbase::core::operations::management::search_get_stats_response&& resp) {
69476960
barrier->set_value(std::move(resp));
69486961
});
69496962
auto resp = cb_wait_for_future(f);

lib/couchbase/errors.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ class DocumentIrretrievable < CouchbaseError
159159
class DocumentLocked < CouchbaseError
160160
end
161161

162+
# Thrown when the server reports that the document is not locked when an unlocking operation is being performed.
163+
class DocumentNotLocked < CouchbaseError
164+
end
165+
162166
# Thrown when the request is too big for some reason.
163167
class ValueTooLarge < CouchbaseError
164168
end

lib/couchbase/protostellar/response_converter/kv.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def self.to_mutate_in_result(resp, specs, options)
106106
Couchbase::Collection::MutateInResult.new do |res|
107107
res.cas = resp.cas
108108
res.transcoder = options.transcoder
109-
res.deleted = nil # TODO: gRPC response has no deleted field
109+
res.deleted = nil # TODO: gRPC response has no deleted field
110110
res.mutation_token = extract_mutation_token(resp)
111111
res.encoded = resp.specs.each_with_index.map do |s, idx|
112112
Couchbase::Collection::SubDocumentField.new do |f|

lib/couchbase/protostellar/response_converter/search.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def self.convert_meta_data(proto_meta_data)
121121
Couchbase::Cluster::SearchMetaData.new do |meta|
122122
proto_metrics = proto_meta_data.metrics
123123
dur = proto_metrics.execution_time
124-
meta.metrics.took = (dur.seconds * 1000) + (dur.nanos / 1000.0).round # `took` is in milliseconds
124+
meta.metrics.took = (dur.seconds * 1000) + (dur.nanos / 1000.0).round # `took` is in milliseconds
125125
meta.metrics.total_rows = proto_metrics.total_rows
126126
meta.metrics.max_score = proto_metrics.max_score
127127
meta.metrics.success_partition_count = proto_metrics.success_partition_count

test/crud_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,26 @@ def test_get_and_lock_protects_document_from_mutations
165165
@collection.upsert(doc_id, document)
166166
end
167167

168+
def test_unlock_document_not_locked
169+
skip("#{name}: Server does not support the not_locked KV status") unless env.server_version.trinity?
170+
skip("#{name}: The mock server does not support the not_locked KV status") if use_caves?
171+
172+
# TODO: Remove protostellar skip once it's added
173+
skip("#{name}: The #{Couchbase::Protostellar::NAME} protocol does not support DocumentNotLocked yet") if env.protostellar?
174+
175+
doc_id = uniq_id(:foo)
176+
document = {"value" => 42}
177+
@collection.upsert(doc_id, document)
178+
179+
res = @collection.get_and_lock(doc_id, 20)
180+
cas = res.cas
181+
@collection.unlock(doc_id, cas)
182+
183+
assert_raises(Couchbase::Error::DocumentNotLocked) do
184+
@collection.unlock(doc_id, cas)
185+
end
186+
end
187+
168188
def test_insert_fails_when_document_exists_already
169189
doc_id = uniq_id(:foo)
170190
document = {"value" => 42}

0 commit comments

Comments
 (0)