couchbase 3.4.0 → 3.4.1
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 +4 -4
- data/README.md +1 -1
- data/ext/couchbase/CMakeLists.txt +8 -3
- data/ext/couchbase/cmake/CompilerWarnings.cmake +12 -4
- data/ext/couchbase/cmake/Documentation.cmake +4 -3
- data/ext/couchbase/cmake/OpenSSL.cmake +52 -7
- data/ext/couchbase/cmake/VersionInfo.cmake +39 -3
- data/ext/couchbase/cmake/test_openssl.cxx +7 -0
- data/ext/couchbase/core/crypto/CMakeLists.txt +5 -1
- data/ext/couchbase/core/impl/collection_query_index_manager.cxx +3 -3
- data/ext/couchbase/core/impl/get_all_query_indexes.cxx +3 -3
- data/ext/couchbase/core/impl/transaction_get_result.cxx +54 -0
- data/ext/couchbase/core/meta/CMakeLists.txt +7 -5
- data/ext/couchbase/core/meta/version.cxx +19 -0
- data/ext/couchbase/core/operations/document_search.cxx +5 -2
- data/ext/couchbase/core/operations/document_search.hxx +0 -1
- data/ext/couchbase/core/transactions/atr_cleanup_entry.cxx +1 -0
- data/ext/couchbase/core/transactions/attempt_context_impl.cxx +62 -31
- data/ext/couchbase/core/transactions/attempt_context_impl.hxx +43 -22
- data/ext/couchbase/core/transactions/forward_compat.hxx +2 -2
- data/ext/couchbase/core/transactions/internal/transaction_context.hxx +1 -1
- data/ext/couchbase/core/transactions/internal/transaction_fields.hxx +1 -0
- data/ext/couchbase/core/transactions/staged_mutation.cxx +1 -1
- data/ext/couchbase/core/transactions/staged_mutation.hxx +12 -2
- data/ext/couchbase/core/transactions/transaction_context.cxx +8 -11
- data/ext/couchbase/core/transactions/transaction_get_result.cxx +41 -31
- data/ext/couchbase/core/transactions/transaction_get_result.hxx +7 -3
- data/ext/couchbase/core/transactions/transaction_links.hxx +13 -1
- data/ext/couchbase/core/transactions/waitable_op_list.hxx +1 -0
- data/ext/couchbase/couchbase/cluster.hxx +2 -2
- data/ext/couchbase/couchbase/cluster_options.hxx +10 -10
- data/ext/couchbase/couchbase/collection.hxx +22 -17
- data/ext/couchbase/couchbase/collection_query_index_manager.hxx +1 -1
- data/ext/couchbase/couchbase/common_options.hxx +1 -1
- data/ext/couchbase/couchbase/configuration_profile.hxx +1 -1
- data/ext/couchbase/couchbase/configuration_profiles_registry.hxx +0 -1
- data/ext/couchbase/couchbase/create_primary_query_index_options.hxx +1 -1
- data/ext/couchbase/couchbase/drop_primary_query_index_options.hxx +1 -1
- data/ext/couchbase/couchbase/drop_query_index_options.hxx +1 -1
- data/ext/couchbase/couchbase/fmt/cas.hxx +12 -0
- data/ext/couchbase/couchbase/fmt/durability_level.hxx +6 -0
- data/ext/couchbase/couchbase/fmt/key_value_extended_error_info.hxx +6 -0
- data/ext/couchbase/couchbase/fmt/key_value_status_code.hxx +6 -0
- data/ext/couchbase/couchbase/fmt/mutation_token.hxx +6 -0
- data/ext/couchbase/couchbase/fmt/query_scan_consistency.hxx +6 -0
- data/ext/couchbase/couchbase/fmt/query_status.hxx +6 -0
- data/ext/couchbase/couchbase/fmt/retry_reason.hxx +6 -0
- data/ext/couchbase/couchbase/fmt/tls_verify_mode.hxx +6 -0
- data/ext/couchbase/couchbase/get_all_query_indexes_options.hxx +5 -4
- data/ext/couchbase/couchbase/query_index_manager.hxx +4 -2
- data/ext/couchbase/couchbase/scope.hxx +1 -1
- data/ext/couchbase/couchbase/subdoc/array_add_unique.hxx +2 -0
- data/ext/couchbase/couchbase/subdoc/array_append.hxx +2 -0
- data/ext/couchbase/couchbase/subdoc/array_insert.hxx +2 -0
- data/ext/couchbase/couchbase/subdoc/array_prepend.hxx +2 -0
- data/ext/couchbase/couchbase/subdoc/count.hxx +2 -0
- data/ext/couchbase/couchbase/subdoc/counter.hxx +2 -0
- data/ext/couchbase/couchbase/subdoc/exists.hxx +2 -0
- data/ext/couchbase/couchbase/subdoc/get.hxx +2 -0
- data/ext/couchbase/couchbase/subdoc/insert.hxx +2 -0
- data/ext/couchbase/couchbase/subdoc/remove.hxx +2 -0
- data/ext/couchbase/couchbase/subdoc/replace.hxx +3 -1
- data/ext/couchbase/couchbase/subdoc/upsert.hxx +2 -0
- data/ext/couchbase/couchbase/transaction_op_error_context.hxx +4 -4
- data/ext/couchbase/couchbase/transactions/transaction_get_result.hxx +36 -51
- data/ext/couchbase/couchbase/transactions/transactions_config.hxx +1 -1
- data/ext/couchbase/test/CMakeLists.txt +2 -0
- data/ext/couchbase/test/test_integration_examples.cxx +141 -0
- data/ext/couchbase/test/test_unit_transaction_utils.cxx +76 -19
- data/ext/couchbase.cxx +479 -20
- data/ext/extconf.rb +2 -1
- data/ext/revisions.rb +3 -2
- data/lib/couchbase/binary_collection.rb +4 -4
- data/lib/couchbase/collection.rb +5 -0
- data/lib/couchbase/errors.rb +10 -0
- data/lib/couchbase/management/collection_query_index_manager.rb +183 -0
- data/lib/couchbase/management/query_index_manager.rb +35 -3
- data/lib/couchbase/management.rb +1 -0
- data/lib/couchbase/options.rb +2 -3
- data/lib/couchbase/version.rb +1 -1
- metadata +9 -5
|
@@ -243,6 +243,7 @@ core::operations::mutate_in_request
|
|
|
243
243
|
attempt_context_impl::create_staging_request(const core::document_id& id,
|
|
244
244
|
const transaction_get_result* document,
|
|
245
245
|
const std::string type,
|
|
246
|
+
const std::string op_id,
|
|
246
247
|
std::optional<std::vector<std::byte>> content)
|
|
247
248
|
{
|
|
248
249
|
core::operations::mutate_in_request req{ id };
|
|
@@ -250,6 +251,7 @@ attempt_context_impl::create_staging_request(const core::document_id& id,
|
|
|
250
251
|
txn["id"] = tao::json::empty_object;
|
|
251
252
|
txn["id"]["txn"] = transaction_id();
|
|
252
253
|
txn["id"]["atmpt"] = this->id();
|
|
254
|
+
txn["id"]["op"] = op_id;
|
|
253
255
|
txn["atr"] = tao::json::empty_object;
|
|
254
256
|
txn["atr"]["id"] = atr_id();
|
|
255
257
|
txn["atr"]["bkt"] = atr_id_->bucket();
|
|
@@ -296,6 +298,7 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
|
|
|
296
298
|
return op_completed_with_error(std::move(cb), transaction_operation_failed(FAIL_OTHER, ec.message()));
|
|
297
299
|
}
|
|
298
300
|
try {
|
|
301
|
+
auto op_id = uid_generator::next();
|
|
299
302
|
// a get can return a 'empty' doc, so check for that and short-circuit the eventual error that will occur...
|
|
300
303
|
if (document.key().empty() || document.bucket().empty()) {
|
|
301
304
|
return op_completed_with_error(std::move(cb),
|
|
@@ -321,7 +324,7 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
|
|
|
321
324
|
check_and_handle_blocking_transactions(
|
|
322
325
|
document,
|
|
323
326
|
forward_compat_stage::WWC_REPLACING,
|
|
324
|
-
[this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), content](
|
|
327
|
+
[this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), op_id, content](
|
|
325
328
|
std::optional<transaction_operation_failed> e1) mutable {
|
|
326
329
|
if (e1) {
|
|
327
330
|
return op_completed_with_error(std::move(cb), *e1);
|
|
@@ -330,7 +333,7 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
|
|
|
330
333
|
document_id{ document.id().bucket(), document.id().scope(), document.id().collection(), document.id().key() };
|
|
331
334
|
select_atr_if_needed_unlocked(
|
|
332
335
|
tmp_doc,
|
|
333
|
-
[this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), content](
|
|
336
|
+
[this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), op_id, content](
|
|
334
337
|
std::optional<transaction_operation_failed> e2) mutable {
|
|
335
338
|
if (e2) {
|
|
336
339
|
return op_completed_with_error(std::move(cb), *e2);
|
|
@@ -339,10 +342,10 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
|
|
|
339
342
|
CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing INSERT of {} while replacing", document);
|
|
340
343
|
exp_delay delay(
|
|
341
344
|
std::chrono::milliseconds(5), std::chrono::milliseconds(300), overall_.config().expiration_time);
|
|
342
|
-
create_staged_insert(document.id(), content, existing_sm->doc().cas().value(), delay, std::move(cb));
|
|
345
|
+
create_staged_insert(document.id(), content, existing_sm->doc().cas().value(), delay, op_id, std::move(cb));
|
|
343
346
|
return;
|
|
344
347
|
}
|
|
345
|
-
create_staged_replace(document, content, std::move(cb));
|
|
348
|
+
create_staged_replace(document, content, op_id, std::move(cb));
|
|
346
349
|
});
|
|
347
350
|
});
|
|
348
351
|
} catch (const client_error& e) {
|
|
@@ -361,9 +364,12 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
|
|
|
361
364
|
|
|
362
365
|
template<typename Handler>
|
|
363
366
|
void
|
|
364
|
-
attempt_context_impl::create_staged_replace(const transaction_get_result& document,
|
|
367
|
+
attempt_context_impl::create_staged_replace(const transaction_get_result& document,
|
|
368
|
+
const std::vector<std::byte>& content,
|
|
369
|
+
const std::string& op_id,
|
|
370
|
+
Handler&& cb)
|
|
365
371
|
{
|
|
366
|
-
auto req = create_staging_request(document.id(), &document, "replace", content);
|
|
372
|
+
auto req = create_staging_request(document.id(), &document, "replace", op_id, content);
|
|
367
373
|
req.cas = document.cas();
|
|
368
374
|
req.access_deleted = true;
|
|
369
375
|
auto error_handler = [this](error_class ec, const std::string& msg, Handler&& cb) {
|
|
@@ -449,6 +455,7 @@ attempt_context_impl::insert_raw(const core::document_id& id, const std::vector<
|
|
|
449
455
|
}
|
|
450
456
|
try {
|
|
451
457
|
check_if_done(cb);
|
|
458
|
+
auto op_id = uid_generator::next();
|
|
452
459
|
staged_mutation* existing_sm = staged_mutations_->find_any(id);
|
|
453
460
|
if ((existing_sm != nullptr) &&
|
|
454
461
|
(existing_sm->type() == staged_mutation_type::INSERT || existing_sm->type() == staged_mutation_type::REPLACE)) {
|
|
@@ -462,17 +469,17 @@ attempt_context_impl::insert_raw(const core::document_id& id, const std::vector<
|
|
|
462
469
|
transaction_operation_failed(FAIL_EXPIRY, "transaction expired").expired());
|
|
463
470
|
}
|
|
464
471
|
select_atr_if_needed_unlocked(
|
|
465
|
-
id, [this, existing_sm, cb = std::move(cb), id, content](std::optional<transaction_operation_failed> err) mutable {
|
|
472
|
+
id, [this, existing_sm, cb = std::move(cb), id, op_id, content](std::optional<transaction_operation_failed> err) mutable {
|
|
466
473
|
if (err) {
|
|
467
474
|
return op_completed_with_error(std::move(cb), *err);
|
|
468
475
|
}
|
|
469
476
|
if (existing_sm != nullptr && existing_sm->type() == staged_mutation_type::REMOVE) {
|
|
470
477
|
CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing remove of {} while inserting", id);
|
|
471
|
-
return create_staged_replace(existing_sm->doc(), content, std::move(cb));
|
|
478
|
+
return create_staged_replace(existing_sm->doc(), content, op_id, std::move(cb));
|
|
472
479
|
}
|
|
473
480
|
uint64_t cas = 0;
|
|
474
481
|
exp_delay delay(std::chrono::milliseconds(5), std::chrono::milliseconds(300), overall_.config().expiration_time);
|
|
475
|
-
create_staged_insert(id, content, cas, delay, std::move(cb));
|
|
482
|
+
create_staged_insert(id, content, cas, delay, op_id, std::move(cb));
|
|
476
483
|
});
|
|
477
484
|
} catch (const std::exception& e) {
|
|
478
485
|
return op_completed_with_error(std::move(cb), transaction_operation_failed(FAIL_OTHER, e.what()));
|
|
@@ -599,6 +606,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
|
|
|
599
606
|
return error_handler(FAIL_EXPIRY, "transaction expired", std::move(cb));
|
|
600
607
|
}
|
|
601
608
|
CB_ATTEMPT_CTX_LOG_DEBUG(this, "removing {}", document);
|
|
609
|
+
auto op_id = uid_generator::next();
|
|
602
610
|
if (existing_sm != nullptr) {
|
|
603
611
|
if (existing_sm->type() == staged_mutation_type::REMOVE) {
|
|
604
612
|
CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing REMOVE of {} while removing", document);
|
|
@@ -616,7 +624,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
|
|
|
616
624
|
check_and_handle_blocking_transactions(
|
|
617
625
|
document,
|
|
618
626
|
forward_compat_stage::WWC_REMOVING,
|
|
619
|
-
[this, document = std::move(document), cb = std::move(cb), error_handler = std::move(error_handler)](
|
|
627
|
+
[this, document = std::move(document), cb = std::move(cb), op_id, error_handler = std::move(error_handler)](
|
|
620
628
|
std::optional<transaction_operation_failed> err1) mutable {
|
|
621
629
|
if (err1) {
|
|
622
630
|
return op_completed_with_error(std::move(cb), *err1);
|
|
@@ -625,7 +633,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
|
|
|
625
633
|
document_id{ document.id().bucket(), document.id().scope(), document.id().collection(), document.id().key() };
|
|
626
634
|
select_atr_if_needed_unlocked(
|
|
627
635
|
tmp_doc,
|
|
628
|
-
[document = std::move(document), cb = std::move(cb), this, error_handler = std::move(error_handler)](
|
|
636
|
+
[document = std::move(document), cb = std::move(cb), this, op_id, error_handler = std::move(error_handler)](
|
|
629
637
|
std::optional<transaction_operation_failed> err2) mutable {
|
|
630
638
|
if (err2) {
|
|
631
639
|
return op_completed_with_error(std::move(cb), *err2);
|
|
@@ -634,7 +642,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
|
|
|
634
642
|
return error_handler(*ec, "before_staged_remove hook raised error", std::move(cb));
|
|
635
643
|
}
|
|
636
644
|
CB_ATTEMPT_CTX_LOG_TRACE(this, "about to remove doc {} with cas {}", document.id(), document.cas().value());
|
|
637
|
-
auto req = create_staging_request(document.id(), &document, "remove");
|
|
645
|
+
auto req = create_staging_request(document.id(), &document, "remove", op_id);
|
|
638
646
|
req.cas = document.cas();
|
|
639
647
|
req.access_deleted = document.links().is_deleted();
|
|
640
648
|
overall_.cluster_ref()->execute(
|
|
@@ -1905,10 +1913,10 @@ attempt_context_impl::set_atr_pending_locked(const core::document_id& id, std::u
|
|
|
1905
1913
|
return fn(std::nullopt);
|
|
1906
1914
|
case FAIL_AMBIGUOUS:
|
|
1907
1915
|
// Retry just this
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1916
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(this, "got FAIL_AMBIGUOUS, retrying set atr pending", ec);
|
|
1917
|
+
return overall_.after_delay(std::chrono::milliseconds(1), [this, doc_id, &lock, fn = std::move(fn)]() {
|
|
1918
|
+
set_atr_pending_locked(doc_id, std::move(lock), std::move(fn));
|
|
1919
|
+
});
|
|
1912
1920
|
case FAIL_TRANSIENT:
|
|
1913
1921
|
// Retry txn
|
|
1914
1922
|
return fn(err.retry());
|
|
@@ -1923,8 +1931,6 @@ attempt_context_impl::set_atr_pending_locked(const core::document_id& id, std::u
|
|
|
1923
1931
|
}
|
|
1924
1932
|
CB_ATTEMPT_CTX_LOG_DEBUG(this, "updating atr {}", atr_id_.value());
|
|
1925
1933
|
|
|
1926
|
-
// FIXME: do we need to capture "now" here?
|
|
1927
|
-
// std::chrono::time_point<std::chrono::steady_clock> now = std::chrono::steady_clock::now();
|
|
1928
1934
|
std::chrono::nanoseconds remaining = overall_.remaining();
|
|
1929
1935
|
// This bounds the value to [0-expirationTime]. It should always be in this range, this is just to protect
|
|
1930
1936
|
// against the application clock changing.
|
|
@@ -2153,6 +2159,7 @@ attempt_context_impl::get_doc(
|
|
|
2153
2159
|
lookup_in_specs::get(ATR_ID).xattr(),
|
|
2154
2160
|
lookup_in_specs::get(TRANSACTION_ID).xattr(),
|
|
2155
2161
|
lookup_in_specs::get(ATTEMPT_ID).xattr(),
|
|
2162
|
+
lookup_in_specs::get(OPERATION_ID).xattr(),
|
|
2156
2163
|
lookup_in_specs::get(STAGED_DATA).xattr(),
|
|
2157
2164
|
lookup_in_specs::get(ATR_BUCKET_NAME).xattr(),
|
|
2158
2165
|
lookup_in_specs::get(ATR_SCOPE_NAME).xattr(),
|
|
@@ -2193,6 +2200,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
|
|
|
2193
2200
|
const std::vector<std::byte>& content,
|
|
2194
2201
|
uint64_t cas,
|
|
2195
2202
|
Delay&& delay,
|
|
2203
|
+
const std::string& op_id,
|
|
2196
2204
|
Handler&& cb,
|
|
2197
2205
|
error_class ec,
|
|
2198
2206
|
const std::string& message)
|
|
@@ -2211,7 +2219,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
|
|
|
2211
2219
|
case FAIL_AMBIGUOUS:
|
|
2212
2220
|
CB_ATTEMPT_CTX_LOG_DEBUG(this, "FAIL_AMBIGUOUS in create_staged_insert, retrying");
|
|
2213
2221
|
delay();
|
|
2214
|
-
return create_staged_insert(id, content, cas, delay, std::forward<Handler>(cb));
|
|
2222
|
+
return create_staged_insert(id, content, cas, delay, op_id, std::forward<Handler>(cb));
|
|
2215
2223
|
case FAIL_OTHER:
|
|
2216
2224
|
return op_completed_with_error(std::forward<Handler>(cb), transaction_operation_failed(ec, "error in create_staged_insert"));
|
|
2217
2225
|
case FAIL_HARD:
|
|
@@ -2221,7 +2229,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
|
|
|
2221
2229
|
case FAIL_CAS_MISMATCH: {
|
|
2222
2230
|
// special handling for doc already existing
|
|
2223
2231
|
CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing doc {}, may still be able to insert", id);
|
|
2224
|
-
auto error_handler = [this, id, content](error_class ec2, const std::string& err_message, Handler&& cb) mutable {
|
|
2232
|
+
auto error_handler = [this, id, op_id, content](error_class ec2, const std::string& err_message, Handler&& cb) mutable {
|
|
2225
2233
|
CB_ATTEMPT_CTX_LOG_TRACE(
|
|
2226
2234
|
this, "after a CAS_MISMATCH or DOC_ALREADY_EXISTS, then got error {} in create_staged_insert", ec2);
|
|
2227
2235
|
if (expiry_overtime_mode_.load()) {
|
|
@@ -2247,7 +2255,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
|
|
|
2247
2255
|
}
|
|
2248
2256
|
return get_doc(
|
|
2249
2257
|
id,
|
|
2250
|
-
[this, id, content, cb = std::forward<Handler>(cb), error_handler, delay](
|
|
2258
|
+
[this, id, content, op_id, cb = std::forward<Handler>(cb), error_handler, delay](
|
|
2251
2259
|
std::optional<error_class> ec3, std::optional<std::string> err_message, std::optional<transaction_get_result> doc) mutable {
|
|
2252
2260
|
if (!ec3) {
|
|
2253
2261
|
if (doc) {
|
|
@@ -2266,7 +2274,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
|
|
|
2266
2274
|
CB_ATTEMPT_CTX_LOG_DEBUG(
|
|
2267
2275
|
this, "create staged insert found existing deleted doc, retrying with cas {}", doc->cas().value());
|
|
2268
2276
|
delay();
|
|
2269
|
-
return create_staged_insert(id, content, doc->cas().value(), delay, std::forward<Handler>(cb));
|
|
2277
|
+
return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
|
|
2270
2278
|
}
|
|
2271
2279
|
if (!doc->links().is_document_in_transaction()) {
|
|
2272
2280
|
// doc was inserted outside txn elsewhere
|
|
@@ -2275,6 +2283,18 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
|
|
|
2275
2283
|
std::forward<Handler>(cb),
|
|
2276
2284
|
document_exists({ couchbase::errc::transaction_op::document_exists_exception, key_value_error_context() }));
|
|
2277
2285
|
}
|
|
2286
|
+
if (doc->links().staged_attempt_id() == this->id()) {
|
|
2287
|
+
if (doc->links().staged_operation_id() == op_id) {
|
|
2288
|
+
// this is us dealing with resolving an ambiguity. So, lets just update the staged_mutation with the
|
|
2289
|
+
// correct cas and continue...
|
|
2290
|
+
staged_mutations_->add(staged_mutation(*doc, content, staged_mutation_type::INSERT));
|
|
2291
|
+
return op_completed_with_callback(std::forward<Handler>(cb), doc);
|
|
2292
|
+
}
|
|
2293
|
+
return op_completed_with_error(
|
|
2294
|
+
std::forward<Handler>(cb),
|
|
2295
|
+
transaction_operation_failed(FAIL_OTHER, "concurrent operations on a document are not allowed")
|
|
2296
|
+
.cause(CONCURRENT_OPERATIONS_DETECTED_ON_SAME_DOCUMENT));
|
|
2297
|
+
}
|
|
2278
2298
|
// CBD-3787 - Only a staged insert is ok to overwrite
|
|
2279
2299
|
if (doc->links().op() && *doc->links().op() != "insert") {
|
|
2280
2300
|
return op_completed_with_error(
|
|
@@ -2285,7 +2305,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
|
|
|
2285
2305
|
check_and_handle_blocking_transactions(
|
|
2286
2306
|
*doc,
|
|
2287
2307
|
forward_compat_stage::WWC_INSERTING,
|
|
2288
|
-
[this, id, content, doc, cb = std::forward<Handler>(cb), delay](
|
|
2308
|
+
[this, id, op_id, content, doc, cb = std::forward<Handler>(cb), delay](
|
|
2289
2309
|
std::optional<transaction_operation_failed> err) mutable {
|
|
2290
2310
|
if (err) {
|
|
2291
2311
|
return op_completed_with_error(std::move(cb), *err);
|
|
@@ -2293,7 +2313,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
|
|
|
2293
2313
|
CB_ATTEMPT_CTX_LOG_DEBUG(
|
|
2294
2314
|
this, "doc ok to overwrite, retrying create_staged_insert with cas {}", doc->cas().value());
|
|
2295
2315
|
delay();
|
|
2296
|
-
return create_staged_insert(id, content, doc->cas().value(), delay, std::forward<Handler>(cb));
|
|
2316
|
+
return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
|
|
2297
2317
|
});
|
|
2298
2318
|
} else {
|
|
2299
2319
|
// no doc now, just retry entire txn
|
|
@@ -2320,20 +2340,27 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
|
|
|
2320
2340
|
const std::vector<std::byte>& content,
|
|
2321
2341
|
uint64_t cas,
|
|
2322
2342
|
Delay&& delay,
|
|
2343
|
+
const std::string& op_id,
|
|
2323
2344
|
Handler&& cb)
|
|
2324
2345
|
{
|
|
2325
2346
|
|
|
2326
2347
|
if (auto ec = error_if_expired_and_not_in_overtime(STAGE_CREATE_STAGED_INSERT, id.key()); ec) {
|
|
2327
|
-
return create_staged_insert_error_handler(
|
|
2328
|
-
|
|
2348
|
+
return create_staged_insert_error_handler(id,
|
|
2349
|
+
content,
|
|
2350
|
+
cas,
|
|
2351
|
+
std::forward<Delay>(delay),
|
|
2352
|
+
op_id,
|
|
2353
|
+
std::forward<Handler>(cb),
|
|
2354
|
+
*ec,
|
|
2355
|
+
"create_staged_insert expired and not in overtime");
|
|
2329
2356
|
}
|
|
2330
2357
|
|
|
2331
2358
|
if (auto ec = hooks_.before_staged_insert(this, id.key()); ec) {
|
|
2332
2359
|
return create_staged_insert_error_handler(
|
|
2333
|
-
id, content, cas, std::forward<Delay>(delay), std::forward<Handler>(cb), *ec, "before_staged_insert hook threw error");
|
|
2360
|
+
id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, "before_staged_insert hook threw error");
|
|
2334
2361
|
}
|
|
2335
2362
|
CB_ATTEMPT_CTX_LOG_DEBUG(this, "about to insert staged doc {} with cas {}", id, cas);
|
|
2336
|
-
auto req = create_staging_request(id, nullptr, "insert", content);
|
|
2363
|
+
auto req = create_staging_request(id, nullptr, "insert", op_id, content);
|
|
2337
2364
|
req.access_deleted = true;
|
|
2338
2365
|
req.create_as_deleted = true;
|
|
2339
2366
|
req.cas = couchbase::cas(cas);
|
|
@@ -2341,11 +2368,13 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
|
|
|
2341
2368
|
wrap_durable_request(req, overall_.config());
|
|
2342
2369
|
overall_.cluster_ref()->execute(
|
|
2343
2370
|
req,
|
|
2344
|
-
[this, id, content, cas, cb = std::forward<Handler>(cb), delay = std::forward<Delay>(delay)](
|
|
2371
|
+
[this, id, content, cas, op_id, cb = std::forward<Handler>(cb), delay = std::forward<Delay>(delay)](
|
|
2345
2372
|
core::operations::mutate_in_response resp) mutable {
|
|
2346
|
-
|
|
2373
|
+
auto ec = resp.ctx.ec() ? error_class_from_response(resp) : hooks_.after_staged_insert_complete(this, id.key());
|
|
2374
|
+
if (ec) {
|
|
2375
|
+
auto msg = (resp.ctx.ec() ? resp.ctx.ec().message() : "after_staged_insert hook threw error");
|
|
2347
2376
|
return create_staged_insert_error_handler(
|
|
2348
|
-
id, content, cas, std::forward<Delay>(delay), std::forward<Handler>(cb), *ec,
|
|
2377
|
+
id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, msg);
|
|
2349
2378
|
}
|
|
2350
2379
|
if (!resp.ctx.ec()) {
|
|
2351
2380
|
CB_ATTEMPT_CTX_LOG_DEBUG(this, "inserted doc {} CAS={}, {}", id, resp.cas.value(), resp.ctx.ec().message());
|
|
@@ -2357,6 +2386,7 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
|
|
|
2357
2386
|
id.collection(),
|
|
2358
2387
|
overall_.transaction_id(),
|
|
2359
2388
|
this->id(),
|
|
2389
|
+
op_id,
|
|
2360
2390
|
content,
|
|
2361
2391
|
std::nullopt,
|
|
2362
2392
|
std::nullopt,
|
|
@@ -2373,6 +2403,7 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
|
|
|
2373
2403
|
content,
|
|
2374
2404
|
cas,
|
|
2375
2405
|
std::forward<Delay>(delay),
|
|
2406
|
+
op_id,
|
|
2376
2407
|
std::forward<Handler>(cb),
|
|
2377
2408
|
error_class_from_response(resp).value(),
|
|
2378
2409
|
resp.ctx.ec().message());
|
|
@@ -301,11 +301,23 @@ class attempt_context_impl
|
|
|
301
301
|
existing_error();
|
|
302
302
|
return func();
|
|
303
303
|
} catch (const async_operation_conflict& e) {
|
|
304
|
-
// can't do anything here but log and eat it.
|
|
305
304
|
CB_ATTEMPT_CTX_LOG_ERROR(this, "Attempted to perform txn operation after commit/rollback started: {}", e.what());
|
|
306
305
|
// you cannot call op_completed_with_error, as it tries to decrement
|
|
307
306
|
// the op count, however it didn't successfully increment it, so...
|
|
308
|
-
|
|
307
|
+
auto err = transaction_operation_failed(FAIL_OTHER, "async operation conflict");
|
|
308
|
+
switch (state()) {
|
|
309
|
+
case attempt_state::ABORTED:
|
|
310
|
+
case attempt_state::ROLLED_BACK:
|
|
311
|
+
err.cause(TRANSACTION_ALREADY_ABORTED);
|
|
312
|
+
break;
|
|
313
|
+
case attempt_state::COMMITTED:
|
|
314
|
+
case attempt_state::COMPLETED:
|
|
315
|
+
err.cause(TRANSACTION_ALREADY_COMMITTED);
|
|
316
|
+
break;
|
|
317
|
+
default:
|
|
318
|
+
err.cause(UNKNOWN);
|
|
319
|
+
}
|
|
320
|
+
op_completed_with_error_no_cache(std::move(cb), std::make_exception_ptr(err));
|
|
309
321
|
} catch (const transaction_operation_failed& e) {
|
|
310
322
|
// thrown only from call_func when previous error exists, so eat it, unless
|
|
311
323
|
// it has PREVIOUS_OP_FAILED cause
|
|
@@ -389,26 +401,25 @@ class attempt_context_impl
|
|
|
389
401
|
std::optional<std::string> query_context,
|
|
390
402
|
couchbase::transactions::async_query_handler&& handler) override
|
|
391
403
|
{
|
|
392
|
-
query(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
404
|
+
query(statement,
|
|
405
|
+
opts,
|
|
406
|
+
query_context,
|
|
407
|
+
[handler = std::move(handler)](std::exception_ptr err, std::optional<core::operations::query_response> resp) {
|
|
408
|
+
if (err) {
|
|
409
|
+
try {
|
|
410
|
+
std::rethrow_exception(err);
|
|
411
|
+
} catch (const transaction_operation_failed& e) {
|
|
412
|
+
return handler(e.get_error_ctx(), {});
|
|
413
|
+
} catch (const op_exception& ex) {
|
|
414
|
+
return handler(ex.ctx(), {});
|
|
415
|
+
} catch (...) {
|
|
416
|
+
// just in case...
|
|
417
|
+
return handler(transaction_op_error_context(couchbase::errc::transaction_op::unknown), {});
|
|
418
|
+
}
|
|
407
419
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
});
|
|
420
|
+
auto [ctx, res] = core::impl::build_transaction_query_result(*resp);
|
|
421
|
+
handler(ctx, res);
|
|
422
|
+
});
|
|
412
423
|
}
|
|
413
424
|
|
|
414
425
|
void commit() override;
|
|
@@ -514,6 +525,7 @@ class attempt_context_impl
|
|
|
514
525
|
core::operations::mutate_in_request create_staging_request(const core::document_id& in,
|
|
515
526
|
const transaction_get_result* document,
|
|
516
527
|
const std::string type,
|
|
528
|
+
const std::string op_id,
|
|
517
529
|
std::optional<std::vector<std::byte>> content = std::nullopt);
|
|
518
530
|
|
|
519
531
|
template<typename Handler, typename Delay>
|
|
@@ -521,16 +533,21 @@ class attempt_context_impl
|
|
|
521
533
|
const std::vector<std::byte>& content,
|
|
522
534
|
uint64_t cas,
|
|
523
535
|
Delay&& delay,
|
|
536
|
+
const std::string& op_id,
|
|
524
537
|
Handler&& cb);
|
|
525
538
|
|
|
526
539
|
template<typename Handler>
|
|
527
|
-
void create_staged_replace(const transaction_get_result& document,
|
|
540
|
+
void create_staged_replace(const transaction_get_result& document,
|
|
541
|
+
const std::vector<std::byte>& content,
|
|
542
|
+
const std::string& op_id,
|
|
543
|
+
Handler&& cb);
|
|
528
544
|
|
|
529
545
|
template<typename Handler, typename Delay>
|
|
530
546
|
void create_staged_insert_error_handler(const core::document_id& id,
|
|
531
547
|
const std::vector<std::byte>& content,
|
|
532
548
|
uint64_t cas,
|
|
533
549
|
Delay&& delay,
|
|
550
|
+
const std::string& op_id,
|
|
534
551
|
Handler&& cb,
|
|
535
552
|
error_class ec,
|
|
536
553
|
const std::string& message);
|
|
@@ -601,6 +618,10 @@ class attempt_context_impl
|
|
|
601
618
|
|
|
602
619
|
void ensure_open_bucket(std::string bucket_name, std::function<void(std::error_code)>&& handler)
|
|
603
620
|
{
|
|
621
|
+
if (bucket_name.empty()) {
|
|
622
|
+
CB_LOG_DEBUG("ensure_open_bucket called with empty bucket_name");
|
|
623
|
+
return handler(couchbase::errc::common::bucket_not_found);
|
|
624
|
+
}
|
|
604
625
|
cluster_ref()->open_bucket(bucket_name, [handler = std::move(handler)](std::error_code ec) { handler(ec); });
|
|
605
626
|
}
|
|
606
627
|
};
|
|
@@ -122,8 +122,8 @@ struct forward_compat_behavior_full {
|
|
|
122
122
|
struct forward_compat_supported {
|
|
123
123
|
uint32_t protocol_major = 2;
|
|
124
124
|
uint32_t protocol_minor = 0;
|
|
125
|
-
std::list<std::string> extensions{ "TI", "MO", "BM",
|
|
126
|
-
"
|
|
125
|
+
std::list<std::string> extensions{ "TI", "MO", "BM", "QU", "SD", "BF3787", "BF3705", "BF3838", "RC",
|
|
126
|
+
"UA", "CO", "BF3791", "CM", "SI", "QC", "IX", "TS" };
|
|
127
127
|
};
|
|
128
128
|
|
|
129
129
|
struct forward_compat_requirement {
|
|
@@ -90,7 +90,7 @@ class transaction_context
|
|
|
90
90
|
|
|
91
91
|
[[nodiscard]] bool has_expired_client_side();
|
|
92
92
|
|
|
93
|
-
void
|
|
93
|
+
void after_delay(std::chrono::milliseconds delay, std::function<void()> fn);
|
|
94
94
|
|
|
95
95
|
[[nodiscard]] std::chrono::time_point<std::chrono::steady_clock> start_time_client() const
|
|
96
96
|
{
|
|
@@ -50,6 +50,7 @@ static const std::string TRANSACTION_RESTORE_PREFIX_ONLY = TRANSACTION_INTERFACE
|
|
|
50
50
|
static const std::string TRANSACTION_RESTORE_PREFIX = TRANSACTION_RESTORE_PREFIX_ONLY + ".";
|
|
51
51
|
static const std::string TRANSACTION_ID = TRANSACTION_INTERFACE_PREFIX + "id.txn";
|
|
52
52
|
static const std::string ATTEMPT_ID = TRANSACTION_INTERFACE_PREFIX + "id.atmpt";
|
|
53
|
+
static const std::string OPERATION_ID = TRANSACTION_INTERFACE_PREFIX + "id.op";
|
|
53
54
|
static const std::string ATR_ID = TRANSACTION_INTERFACE_PREFIX + "atr.id";
|
|
54
55
|
static const std::string ATR_BUCKET_NAME = TRANSACTION_INTERFACE_PREFIX + "atr.bkt";
|
|
55
56
|
|
|
@@ -315,7 +315,7 @@ staged_mutation_queue::commit_doc(attempt_context_impl* ctx, staged_mutation& it
|
|
|
315
315
|
|
|
316
316
|
result res;
|
|
317
317
|
if (item.type() == staged_mutation_type::INSERT && !cas_zero_mode) {
|
|
318
|
-
core::operations::insert_request req{ item.doc().id(), item.
|
|
318
|
+
core::operations::insert_request req{ item.doc().id(), item.content() };
|
|
319
319
|
req.flags = couchbase::codec::codec_flags::json_common_flags;
|
|
320
320
|
wrap_durable_request(req, ctx->overall_.config());
|
|
321
321
|
auto barrier = std::make_shared<std::promise<result>>();
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
#pragma once
|
|
18
18
|
|
|
19
19
|
#include "attempt_context_impl.hxx"
|
|
20
|
-
|
|
21
20
|
#include "internal/utils.hxx"
|
|
22
21
|
#include "transaction_get_result.hxx"
|
|
22
|
+
#include "uid_generator.hxx"
|
|
23
23
|
|
|
24
24
|
#include <mutex>
|
|
25
25
|
#include <string>
|
|
@@ -35,13 +35,18 @@ class staged_mutation
|
|
|
35
35
|
transaction_get_result doc_;
|
|
36
36
|
staged_mutation_type type_;
|
|
37
37
|
std::vector<std::byte> content_;
|
|
38
|
+
std::string operation_id_;
|
|
38
39
|
|
|
39
40
|
public:
|
|
40
41
|
template<typename Content>
|
|
41
|
-
staged_mutation(transaction_get_result& doc,
|
|
42
|
+
staged_mutation(transaction_get_result& doc,
|
|
43
|
+
Content content,
|
|
44
|
+
staged_mutation_type type,
|
|
45
|
+
std::string operation_id = uid_generator::next())
|
|
42
46
|
: doc_(doc)
|
|
43
47
|
, type_(type)
|
|
44
48
|
, content_(std::move(content))
|
|
49
|
+
, operation_id_(std::move(operation_id))
|
|
45
50
|
{
|
|
46
51
|
}
|
|
47
52
|
|
|
@@ -97,6 +102,11 @@ class staged_mutation
|
|
|
97
102
|
}
|
|
98
103
|
throw std::runtime_error("unknown type of staged mutation");
|
|
99
104
|
}
|
|
105
|
+
|
|
106
|
+
[[nodiscard]] const std::string& operation_id() const
|
|
107
|
+
{
|
|
108
|
+
return operation_id_;
|
|
109
|
+
}
|
|
100
110
|
};
|
|
101
111
|
|
|
102
112
|
class staged_mutation_queue
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
#include "attempt_context_impl.hxx"
|
|
18
18
|
#include "uid_generator.hxx"
|
|
19
19
|
#include <asio/post.hpp>
|
|
20
|
-
#include <
|
|
20
|
+
#include <asio/steady_timer.hpp>
|
|
21
21
|
|
|
22
22
|
#include "internal/logging.hxx"
|
|
23
23
|
#include "internal/transaction_context.hxx"
|
|
@@ -78,17 +78,14 @@ transaction_context::has_expired_client_side()
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
void
|
|
81
|
-
transaction_context::
|
|
81
|
+
transaction_context::after_delay(std::chrono::milliseconds delay, std::function<void()> fn)
|
|
82
82
|
{
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
CB_ATTEMPT_CTX_LOG_TRACE(
|
|
90
|
-
current_attempt_context_, "about to sleep for {} ms", std::chrono::duration_cast<std::chrono::milliseconds>(delay).count());
|
|
91
|
-
std::this_thread::sleep_for(delay);
|
|
83
|
+
auto timer = std::make_shared<asio::steady_timer>(this->transactions_.cluster_ref()->io_context());
|
|
84
|
+
timer->expires_after(delay);
|
|
85
|
+
timer->async_wait([timer, fn](std::error_code) {
|
|
86
|
+
// have to always call the function, even if timer was canceled.
|
|
87
|
+
fn();
|
|
88
|
+
});
|
|
92
89
|
}
|
|
93
90
|
|
|
94
91
|
void
|