couchbase 4.2.11 → 4.3.0
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.
- package/deps/couchbase-cxx-cache/mozilla-ca-bundle.crt +49 -2
- package/deps/couchbase-cxx-cache/mozilla-ca-bundle.sha256 +1 -1
- package/deps/couchbase-cxx-client/core/impl/cluster.cxx +51 -5
- package/deps/couchbase-cxx-client/core/impl/collection.cxx +224 -209
- package/deps/couchbase-cxx-client/core/impl/query_error_context.cxx +2 -2
- package/deps/couchbase-cxx-client/core/impl/query_index_manager.cxx +1 -0
- package/deps/couchbase-cxx-client/core/io/dns_client.cxx +4 -0
- package/deps/couchbase-cxx-client/core/io/dns_config.cxx +15 -4
- package/deps/couchbase-cxx-client/core/io/dns_config.hxx +1 -1
- package/deps/couchbase-cxx-client/core/io/mcbp_session.cxx +84 -53
- package/deps/couchbase-cxx-client/core/mcbp/operation_queue.cxx +1 -0
- package/deps/couchbase-cxx-client/core/meta/features.hxx +5 -0
- package/deps/couchbase-cxx-client/core/operations/document_lookup_in_all_replicas.hxx +116 -105
- package/deps/couchbase-cxx-client/core/operations/document_lookup_in_any_replica.hxx +116 -108
- package/deps/couchbase-cxx-client/core/operations/document_search.cxx +97 -81
- package/deps/couchbase-cxx-client/core/operations/document_search.hxx +5 -0
- package/deps/couchbase-cxx-client/core/range_scan_orchestrator_options.hxx +2 -1
- package/deps/couchbase-cxx-client/core/transactions/atr_cleanup_entry.cxx +16 -7
- package/deps/couchbase-cxx-client/core/transactions/attempt_context_impl.cxx +578 -483
- package/deps/couchbase-cxx-client/core/transactions/attempt_context_testing_hooks.cxx +51 -50
- package/deps/couchbase-cxx-client/core/transactions/attempt_context_testing_hooks.hxx +4 -2
- package/deps/couchbase-cxx-client/core/transactions/cleanup_testing_hooks.cxx +6 -6
- package/deps/couchbase-cxx-client/core/transactions/cleanup_testing_hooks.hxx +3 -2
- package/deps/couchbase-cxx-client/core/transactions/internal/transactions_cleanup.hxx +2 -0
- package/deps/couchbase-cxx-client/core/transactions/internal/utils.hxx +5 -1
- package/deps/couchbase-cxx-client/core/transactions/staged_mutation.cxx +222 -179
- package/deps/couchbase-cxx-client/core/transactions/staged_mutation.hxx +23 -12
- package/deps/couchbase-cxx-client/core/transactions/transactions.cxx +61 -24
- package/deps/couchbase-cxx-client/core/transactions/transactions_cleanup.cxx +36 -16
- package/deps/couchbase-cxx-client/core/transactions/utils.cxx +9 -0
- package/deps/couchbase-cxx-client/core/transactions.hxx +40 -7
- package/deps/couchbase-cxx-client/couchbase/cluster.hxx +19 -0
- package/deps/couchbase-cxx-client/couchbase/fork_event.hxx +39 -0
- package/dist/binding.d.ts +8 -0
- package/dist/bindingutilities.d.ts +6 -1
- package/dist/bindingutilities.js +15 -1
- package/dist/bucketmanager.d.ts +0 -12
- package/dist/cluster.d.ts +0 -2
- package/dist/cluster.js +0 -2
- package/dist/collection.d.ts +3 -3
- package/dist/collection.js +3 -1
- package/dist/querytypes.d.ts +0 -2
- package/dist/rangeScan.d.ts +0 -8
- package/dist/rangeScan.js +0 -8
- package/dist/scope.d.ts +0 -5
- package/dist/scope.js +0 -5
- package/dist/scopesearchindexmanager.d.ts +0 -2
- package/dist/scopesearchindexmanager.js +0 -2
- package/dist/searchexecutor.js +3 -1
- package/dist/searchtypes.d.ts +16 -6
- package/dist/searchtypes.js +2 -6
- package/dist/transactions.d.ts +23 -0
- package/dist/transactions.js +16 -10
- package/dist/vectorsearch.d.ts +8 -8
- package/dist/vectorsearch.js +7 -7
- package/package.json +7 -7
- package/src/instance.cpp +11 -1
- package/src/instance.hpp +1 -0
- package/src/jstocbpp_autogen.hpp +8 -0
- package/src/jstocbpp_transactions.hpp +40 -3
- package/src/transactions.cpp +12 -1
- package/tools/gen-bindings-json.py +0 -1
@@ -117,49 +117,53 @@ attempt_context_impl::get(const core::document_id& id, Callback&& cb)
|
|
117
117
|
}
|
118
118
|
cache_error_async(cb, [&]() mutable {
|
119
119
|
check_if_done(cb);
|
120
|
-
do_get(
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
120
|
+
do_get(id,
|
121
|
+
std::nullopt,
|
122
|
+
[this, id, cb = std::move(cb)](
|
123
|
+
std::optional<error_class> ec, std::optional<std::string> err_message, std::optional<transaction_get_result> res) mutable {
|
124
|
+
auto handler = [this, id, err_message, res, cb = std::move(cb)](std::optional<error_class> ec) mutable {
|
125
|
+
if (ec) {
|
126
|
+
switch (*ec) {
|
127
|
+
case FAIL_EXPIRY:
|
128
|
+
return op_completed_with_error(
|
129
|
+
std::move(cb), transaction_operation_failed(*ec, "transaction expired during get").expired());
|
130
|
+
case FAIL_DOC_NOT_FOUND:
|
131
|
+
return op_completed_with_error(
|
132
|
+
std::move(cb),
|
133
|
+
transaction_operation_failed(*ec, fmt::format("document not found {}", err_message.value_or("")))
|
134
|
+
.cause(external_exception::DOCUMENT_NOT_FOUND_EXCEPTION));
|
135
|
+
case FAIL_TRANSIENT:
|
136
|
+
return op_completed_with_error(
|
137
|
+
std::move(cb),
|
138
|
+
transaction_operation_failed(*ec, fmt::format("transient failure in get {}", err_message.value_or("")))
|
139
|
+
.retry());
|
140
|
+
case FAIL_HARD:
|
141
|
+
return op_completed_with_error(
|
142
|
+
std::move(cb),
|
143
|
+
transaction_operation_failed(*ec, fmt::format("fail hard in get {}", err_message.value_or("")))
|
144
|
+
.no_rollback());
|
145
|
+
default: {
|
146
|
+
auto msg = fmt::format("got error \"{}\" while getting doc {}", err_message.value_or(""), id.key());
|
147
|
+
return op_completed_with_error(std::move(cb), transaction_operation_failed(FAIL_OTHER, msg));
|
148
|
+
}
|
149
|
+
}
|
150
|
+
} else {
|
151
|
+
if (!res) {
|
152
|
+
return op_completed_with_error(std::move(cb), transaction_operation_failed(*ec, "document not found"));
|
153
|
+
}
|
154
|
+
auto err = forward_compat::check(forward_compat_stage::GETS, res->links().forward_compat());
|
155
|
+
if (err) {
|
156
|
+
return op_completed_with_error(std::move(cb), *err);
|
157
|
+
}
|
158
|
+
return op_completed_with_callback(std::move(cb), res);
|
159
|
+
}
|
160
|
+
};
|
161
|
+
|
162
|
+
if (!ec) {
|
163
|
+
return hooks_.after_get_complete(this, id.key(), std::move(handler));
|
164
|
+
}
|
165
|
+
return handler(ec);
|
166
|
+
});
|
163
167
|
});
|
164
168
|
}
|
165
169
|
|
@@ -190,51 +194,55 @@ attempt_context_impl::get_optional(const core::document_id& id, Callback&& cb)
|
|
190
194
|
return op_completed_with_error(std::move(cb), transaction_operation_failed(FAIL_OTHER, ec.message()));
|
191
195
|
}
|
192
196
|
check_if_done(cb);
|
193
|
-
do_get(
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
197
|
+
do_get(
|
198
|
+
id,
|
199
|
+
std::nullopt,
|
200
|
+
[this, id, cb = std::move(cb)](
|
201
|
+
std::optional<error_class> ec, std::optional<std::string> err_message, std::optional<transaction_get_result> res) mutable {
|
202
|
+
auto handler = [this, id, err_message, res, cb = std::move(cb)](std::optional<error_class> ec) mutable {
|
203
|
+
if (ec) {
|
204
|
+
switch (*ec) {
|
205
|
+
case FAIL_EXPIRY:
|
206
|
+
return op_completed_with_error(
|
207
|
+
std::move(cb),
|
208
|
+
transaction_operation_failed(*ec,
|
209
|
+
fmt::format("transaction expired during get {}", err_message.value_or("")))
|
210
|
+
.expired());
|
211
|
+
case FAIL_DOC_NOT_FOUND:
|
212
|
+
return op_completed_with_callback(std::move(cb), std::optional<transaction_get_result>());
|
213
|
+
case FAIL_TRANSIENT:
|
214
|
+
return op_completed_with_error(
|
215
|
+
std::move(cb),
|
216
|
+
transaction_operation_failed(*ec, fmt::format("transient failure in get {}", err_message.value_or("")))
|
217
|
+
.retry());
|
218
|
+
case FAIL_HARD:
|
219
|
+
return op_completed_with_error(
|
220
|
+
std::move(cb),
|
221
|
+
transaction_operation_failed(*ec, fmt::format("fail hard in get {}", err_message.value_or("")))
|
222
|
+
.no_rollback());
|
223
|
+
default: {
|
224
|
+
return op_completed_with_error(
|
225
|
+
std::move(cb),
|
226
|
+
transaction_operation_failed(FAIL_OTHER,
|
227
|
+
fmt::format("error getting {} {}", id.key(), err_message.value_or(""))));
|
228
|
+
}
|
229
|
+
}
|
230
|
+
} else {
|
231
|
+
if (res) {
|
232
|
+
auto err = forward_compat::check(forward_compat_stage::GETS, res->links().forward_compat());
|
233
|
+
if (err) {
|
234
|
+
return op_completed_with_error(std::move(cb), *err);
|
235
|
+
}
|
236
|
+
}
|
237
|
+
return op_completed_with_callback(std::move(cb), res);
|
238
|
+
}
|
239
|
+
};
|
240
|
+
|
241
|
+
if (!ec) {
|
242
|
+
return hooks_.after_get_complete(this, id.key(), std::move(handler));
|
243
|
+
}
|
244
|
+
return handler(ec);
|
245
|
+
});
|
238
246
|
});
|
239
247
|
});
|
240
248
|
}
|
@@ -386,7 +394,8 @@ attempt_context_impl::create_staged_replace(const transaction_get_result& docume
|
|
386
394
|
return op_completed_with_error(std::move(cb), err);
|
387
395
|
}
|
388
396
|
};
|
389
|
-
auto ec =
|
397
|
+
auto ec = wait_for_hook(
|
398
|
+
[this, key = document.id().key()](auto handler) mutable { return hooks_.before_staged_replace(this, key, std::move(handler)); });
|
390
399
|
if (ec) {
|
391
400
|
return error_handler(*ec, "before_staged_replace hook raised error", std::move(cb));
|
392
401
|
}
|
@@ -394,21 +403,27 @@ attempt_context_impl::create_staged_replace(const transaction_get_result& docume
|
|
394
403
|
this, "about to replace doc {} with cas {} in txn {}", document.id(), document.cas().value(), overall_.transaction_id());
|
395
404
|
overall_.cluster_ref().execute(
|
396
405
|
req,
|
397
|
-
[this, document
|
406
|
+
[this, document, content, cb = std::move(cb), error_handler = std::move(error_handler)](
|
398
407
|
core::operations::mutate_in_response resp) mutable {
|
399
408
|
if (auto ec2 = error_class_from_response(resp); ec2) {
|
400
409
|
return error_handler(*ec2, resp.ctx.ec().message(), std::move(cb));
|
401
410
|
}
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
411
|
+
return hooks_.after_staged_replace_complete(
|
412
|
+
this,
|
413
|
+
document.id().key(),
|
414
|
+
[this, document, content, error_handler = std::move(error_handler), cb = std::move(cb), resp = std::move(resp)](
|
415
|
+
auto ec) mutable {
|
416
|
+
if (ec) {
|
417
|
+
return error_handler(*ec, "after_staged_replace_commit hook returned error", std::move(cb));
|
418
|
+
}
|
419
|
+
|
420
|
+
transaction_get_result out = document;
|
421
|
+
out.cas(resp.cas.value());
|
422
|
+
out.content(content);
|
423
|
+
CB_ATTEMPT_CTX_LOG_TRACE(this, "replace staged content, result {}", out);
|
424
|
+
staged_mutations_->add(staged_mutation(out, content, staged_mutation_type::REPLACE));
|
425
|
+
return op_completed_with_callback(std::move(cb), std::optional<transaction_get_result>(out));
|
426
|
+
});
|
412
427
|
});
|
413
428
|
}
|
414
429
|
|
@@ -523,47 +538,51 @@ attempt_context_impl::check_atr_entry_for_blocking_document(const transaction_ge
|
|
523
538
|
{
|
524
539
|
try {
|
525
540
|
delay();
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
doc.links().atr_scope_name().value(),
|
531
|
-
doc.links().atr_collection_name().value(),
|
532
|
-
doc.links().atr_id().value());
|
533
|
-
active_transaction_record::get_atr(
|
534
|
-
cluster_ref(),
|
535
|
-
atr_id,
|
536
|
-
[this, delay = std::move(delay), cb = std::move(cb), doc = std::move(doc)](std::error_code err,
|
537
|
-
std::optional<active_transaction_record> atr) mutable {
|
538
|
-
if (!err) {
|
539
|
-
if (atr) {
|
540
|
-
auto entries = atr->entries();
|
541
|
-
auto it = std::find_if(entries.begin(), entries.end(), [&doc](const atr_entry& e) {
|
542
|
-
return e.attempt_id() == doc.links().staged_attempt_id();
|
543
|
-
});
|
544
|
-
if (it != entries.end()) {
|
545
|
-
auto fwd_err = forward_compat::check(forward_compat_stage::WWC_READING_ATR, it->forward_compat());
|
546
|
-
if (fwd_err) {
|
547
|
-
return cb(fwd_err);
|
548
|
-
}
|
549
|
-
switch (it->state()) {
|
550
|
-
case attempt_state::COMPLETED:
|
551
|
-
case attempt_state::ROLLED_BACK:
|
552
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(
|
553
|
-
this, "existing atr entry can be ignored due to state {}", attempt_state_name(it->state()));
|
554
|
-
return cb(std::nullopt);
|
555
|
-
default:
|
556
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(
|
557
|
-
this, "existing atr entry found in state {}, retrying", attempt_state_name(it->state()));
|
558
|
-
}
|
559
|
-
return check_atr_entry_for_blocking_document(doc, delay, std::move(cb));
|
560
|
-
}
|
561
|
-
}
|
562
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(this, "no blocking atr entry");
|
563
|
-
return cb(std::nullopt);
|
541
|
+
return hooks_.before_check_atr_entry_for_blocking_doc(
|
542
|
+
this, doc.id().key(), [this, delay = std::move(delay), cb = std::move(cb), doc = std::move(doc)](auto ec) mutable {
|
543
|
+
if (ec) {
|
544
|
+
return cb(transaction_operation_failed(FAIL_WRITE_WRITE_CONFLICT, "document is in another transaction").retry());
|
564
545
|
}
|
565
|
-
|
566
|
-
|
546
|
+
|
547
|
+
core::document_id atr_id(doc.links().atr_bucket_name().value(),
|
548
|
+
doc.links().atr_scope_name().value(),
|
549
|
+
doc.links().atr_collection_name().value(),
|
550
|
+
doc.links().atr_id().value());
|
551
|
+
return active_transaction_record::get_atr(
|
552
|
+
cluster_ref(),
|
553
|
+
atr_id,
|
554
|
+
[this, delay = std::move(delay), cb = std::move(cb), doc = std::move(doc)](
|
555
|
+
std::error_code err, std::optional<active_transaction_record> atr) mutable {
|
556
|
+
if (!err) {
|
557
|
+
if (atr) {
|
558
|
+
auto entries = atr->entries();
|
559
|
+
auto it = std::find_if(entries.begin(), entries.end(), [&doc](const atr_entry& e) {
|
560
|
+
return e.attempt_id() == doc.links().staged_attempt_id();
|
561
|
+
});
|
562
|
+
if (it != entries.end()) {
|
563
|
+
auto fwd_err = forward_compat::check(forward_compat_stage::WWC_READING_ATR, it->forward_compat());
|
564
|
+
if (fwd_err) {
|
565
|
+
return cb(fwd_err);
|
566
|
+
}
|
567
|
+
switch (it->state()) {
|
568
|
+
case attempt_state::COMPLETED:
|
569
|
+
case attempt_state::ROLLED_BACK:
|
570
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(
|
571
|
+
this, "existing atr entry can be ignored due to state {}", attempt_state_name(it->state()));
|
572
|
+
return cb(std::nullopt);
|
573
|
+
default:
|
574
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(
|
575
|
+
this, "existing atr entry found in state {}, retrying", attempt_state_name(it->state()));
|
576
|
+
}
|
577
|
+
return check_atr_entry_for_blocking_document(doc, delay, std::move(cb));
|
578
|
+
}
|
579
|
+
}
|
580
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(this, "no blocking atr entry");
|
581
|
+
return cb(std::nullopt);
|
582
|
+
}
|
583
|
+
// if we are here, there is still a write-write conflict
|
584
|
+
return cb(transaction_operation_failed(FAIL_WRITE_WRITE_CONFLICT, "document is in another transaction").retry());
|
585
|
+
});
|
567
586
|
});
|
568
587
|
} catch (const retry_operation_timeout&) {
|
569
588
|
return cb(transaction_operation_failed(FAIL_WRITE_WRITE_CONFLICT, "document is in another transaction").retry());
|
@@ -620,7 +639,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
|
|
620
639
|
return;
|
621
640
|
}
|
622
641
|
}
|
623
|
-
check_and_handle_blocking_transactions(
|
642
|
+
return check_and_handle_blocking_transactions(
|
624
643
|
document,
|
625
644
|
forward_compat_stage::WWC_REMOVING,
|
626
645
|
[this, document = std::move(document), cb = std::move(cb), op_id, error_handler = std::move(error_handler)](
|
@@ -630,38 +649,57 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
|
|
630
649
|
}
|
631
650
|
auto tmp_doc =
|
632
651
|
document_id{ document.id().bucket(), document.id().scope(), document.id().collection(), document.id().key() };
|
633
|
-
select_atr_if_needed_unlocked(
|
652
|
+
return select_atr_if_needed_unlocked(
|
634
653
|
tmp_doc,
|
635
654
|
[document = std::move(document), cb = std::move(cb), this, op_id, error_handler = std::move(error_handler)](
|
636
655
|
std::optional<transaction_operation_failed> err2) mutable {
|
637
656
|
if (err2) {
|
638
657
|
return op_completed_with_error(std::move(cb), *err2);
|
639
658
|
}
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
req,
|
649
|
-
[this, document = std::move(document), cb = std::move(cb), error_handler = std::move(error_handler)](
|
650
|
-
core::operations::mutate_in_response resp) mutable {
|
651
|
-
auto ec = error_class_from_response(resp);
|
652
|
-
if (!ec) {
|
653
|
-
ec = hooks_.after_staged_remove_complete(this, document.id().key());
|
654
|
-
}
|
655
|
-
if (!ec) {
|
656
|
-
CB_ATTEMPT_CTX_LOG_TRACE(
|
657
|
-
this, "removed doc {} CAS={}, rc={}", document.id(), resp.cas.value(), resp.ctx.ec().message());
|
658
|
-
// TODO: this copy... can we do better?
|
659
|
-
transaction_get_result new_res = document;
|
660
|
-
new_res.cas(resp.cas.value());
|
661
|
-
staged_mutations_->add(staged_mutation(new_res, std::vector<std::byte>{}, staged_mutation_type::REMOVE));
|
662
|
-
return op_completed_with_callback(cb);
|
659
|
+
auto key = document.id().key();
|
660
|
+
return hooks_.before_staged_remove(
|
661
|
+
this,
|
662
|
+
key,
|
663
|
+
[document = std::move(document), cb = std::move(cb), this, op_id, error_handler = std::move(error_handler)](
|
664
|
+
auto ec) mutable {
|
665
|
+
if (ec) {
|
666
|
+
return error_handler(*ec, "before_staged_remove hook raised error", std::move(cb));
|
663
667
|
}
|
664
|
-
|
668
|
+
CB_ATTEMPT_CTX_LOG_TRACE(this, "about to remove doc {} with cas {}", document.id(), document.cas().value());
|
669
|
+
auto req = create_staging_request(document.id(), &document, "remove", op_id);
|
670
|
+
req.cas = document.cas();
|
671
|
+
req.access_deleted = document.links().is_deleted();
|
672
|
+
return overall_.cluster_ref().execute(
|
673
|
+
req,
|
674
|
+
[this, document = std::move(document), cb = std::move(cb), error_handler = std::move(error_handler)](
|
675
|
+
core::operations::mutate_in_response resp) mutable {
|
676
|
+
auto ec = error_class_from_response(resp);
|
677
|
+
if (ec) {
|
678
|
+
return error_handler(*ec, resp.ctx.ec().message(), std::move(cb));
|
679
|
+
}
|
680
|
+
auto key = document.id().key();
|
681
|
+
return hooks_.after_staged_remove_complete(
|
682
|
+
this,
|
683
|
+
key,
|
684
|
+
[this,
|
685
|
+
document = std::move(document),
|
686
|
+
cb = std::move(cb),
|
687
|
+
error_handler = std::move(error_handler),
|
688
|
+
resp = std::move(resp)](auto ec) mutable {
|
689
|
+
if (ec) {
|
690
|
+
return error_handler(*ec, resp.ctx.ec().message(), std::move(cb));
|
691
|
+
}
|
692
|
+
|
693
|
+
CB_ATTEMPT_CTX_LOG_TRACE(
|
694
|
+
this, "removed doc {} CAS={}, rc={}", document.id(), resp.cas.value(), resp.ctx.ec().message());
|
695
|
+
// TODO: this copy... can we do better?
|
696
|
+
transaction_get_result new_res = document;
|
697
|
+
new_res.cas(resp.cas.value());
|
698
|
+
staged_mutations_->add(
|
699
|
+
staged_mutation(new_res, std::vector<std::byte>{}, staged_mutation_type::REMOVE));
|
700
|
+
return op_completed_with_callback(cb);
|
701
|
+
});
|
702
|
+
});
|
665
703
|
});
|
666
704
|
});
|
667
705
|
});
|
@@ -688,36 +726,41 @@ attempt_context_impl::remove_staged_insert(const core::document_id& id, VoidCall
|
|
688
726
|
};
|
689
727
|
CB_ATTEMPT_CTX_LOG_DEBUG(this, "removing staged insert {}", id);
|
690
728
|
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
729
|
+
return hooks_.before_remove_staged_insert(
|
730
|
+
this, id.key(), [this, id = std::move(id), cb = std::move(cb), error_handler = std::move(error_handler)](auto ec) mutable {
|
731
|
+
if (ec) {
|
732
|
+
return error_handler(*ec, "before_remove_staged_insert hook returned error", std::move(cb));
|
733
|
+
}
|
734
|
+
core::operations::mutate_in_request req{ id };
|
735
|
+
req.specs =
|
736
|
+
couchbase::mutate_in_specs{
|
737
|
+
couchbase::mutate_in_specs::remove("txn").xattr(),
|
738
|
+
}
|
739
|
+
.specs();
|
740
|
+
wrap_durable_request(req, overall_.config());
|
741
|
+
req.access_deleted = true;
|
742
|
+
|
743
|
+
return overall_.cluster_ref().execute(
|
744
|
+
req,
|
745
|
+
[this, id = std::move(id), cb = std::move(cb), error_handler = std::move(error_handler)](
|
746
|
+
core::operations::mutate_in_response resp) mutable {
|
747
|
+
auto ec = error_class_from_response(resp);
|
748
|
+
if (ec) {
|
749
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(this, "remove_staged_insert got error {}", *ec);
|
750
|
+
return error_handler(*ec, resp.ctx.ec().message(), std::move(cb));
|
751
|
+
}
|
752
|
+
return hooks_.after_remove_staged_insert(
|
753
|
+
this,
|
754
|
+
id.key(),
|
755
|
+
[this, id = std::move(id), cb = std::move(cb), error_handler = std::move(error_handler)](auto ec) mutable {
|
756
|
+
if (ec) {
|
757
|
+
return error_handler(*ec, "after_remove_staged_insert hook returned error", std::move(cb));
|
758
|
+
}
|
759
|
+
staged_mutations_->remove_any(id);
|
760
|
+
return op_completed_with_callback(std::move(cb));
|
761
|
+
});
|
762
|
+
});
|
763
|
+
});
|
721
764
|
}
|
722
765
|
|
723
766
|
void
|
@@ -1018,21 +1061,26 @@ attempt_context_impl::wrap_query(const std::string& statement,
|
|
1018
1061
|
req.raw["txdata"] = core::utils::json::generate(txdata);
|
1019
1062
|
}
|
1020
1063
|
req.statement = statement;
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
CB_ATTEMPT_CTX_LOG_TRACE(this, "http request: {}", dump_request(req));
|
1029
|
-
overall_.cluster_ref().execute(req, [this, cb = std::move(cb)](core::operations::query_response resp) mutable {
|
1030
|
-
CB_ATTEMPT_CTX_LOG_TRACE(this, "response: {} status: {}", resp.ctx.http_body, resp.meta.status);
|
1031
|
-
if (auto ec = hooks_.after_query(this, resp.ctx.statement)) {
|
1032
|
-
auto err = std::make_exception_ptr(transaction_operation_failed(*ec, "after_query hook raised error"));
|
1033
|
-
return cb(err, {});
|
1064
|
+
return hooks_.before_query(this, statement, [this, statement, req, cb = std::move(cb)](auto ec) mutable {
|
1065
|
+
if (ec) {
|
1066
|
+
auto err = std::make_exception_ptr(transaction_operation_failed(*ec, "before_query hook raised error"));
|
1067
|
+
if (statement == BEGIN_WORK) {
|
1068
|
+
return cb(std::make_exception_ptr(transaction_operation_failed(*ec, "before_query hook raised error").no_rollback()), {});
|
1069
|
+
}
|
1070
|
+
return cb(std::make_exception_ptr(transaction_operation_failed(*ec, "before_query hook raised error")), {});
|
1034
1071
|
}
|
1035
|
-
|
1072
|
+
|
1073
|
+
CB_ATTEMPT_CTX_LOG_TRACE(this, "http request: {}", dump_request(req));
|
1074
|
+
return overall_.cluster_ref().execute(req, [this, cb = std::move(cb)](core::operations::query_response resp) mutable {
|
1075
|
+
CB_ATTEMPT_CTX_LOG_TRACE(this, "response: {} status: {}", resp.ctx.http_body, resp.meta.status);
|
1076
|
+
return hooks_.after_query(this, resp.ctx.statement, [this, resp = std::move(resp), cb = std::move(cb)](auto ec) mutable {
|
1077
|
+
if (ec) {
|
1078
|
+
auto err = std::make_exception_ptr(transaction_operation_failed(*ec, "after_query hook raised error"));
|
1079
|
+
return cb(err, {});
|
1080
|
+
}
|
1081
|
+
return cb(handle_query_error(resp), resp);
|
1082
|
+
});
|
1083
|
+
});
|
1036
1084
|
});
|
1037
1085
|
}
|
1038
1086
|
|
@@ -1389,7 +1437,8 @@ attempt_context_impl::atr_commit(bool ambiguity_resolution_mode)
|
|
1389
1437
|
if (ec) {
|
1390
1438
|
throw client_error(*ec, "atr_commit check for expiry threw error");
|
1391
1439
|
}
|
1392
|
-
|
1440
|
+
ec = wait_for_hook([this](auto handler) mutable { return hooks_.before_atr_commit(this, std::move(handler)); });
|
1441
|
+
if (ec) {
|
1393
1442
|
// for now, throw. Later, if this is async, we will use error handler no doubt.
|
1394
1443
|
throw client_error(*ec, "before_atr_commit hook raised error");
|
1395
1444
|
}
|
@@ -1400,7 +1449,7 @@ attempt_context_impl::atr_commit(bool ambiguity_resolution_mode)
|
|
1400
1449
|
overall_.cluster_ref().execute(
|
1401
1450
|
req, [barrier](core::operations::mutate_in_response resp) { barrier->set_value(result::create_from_subdoc_response(resp)); });
|
1402
1451
|
auto res = wrap_operation_future(f, false);
|
1403
|
-
ec = hooks_.after_atr_commit(this);
|
1452
|
+
ec = wait_for_hook([this](auto handler) mutable { return hooks_.after_atr_commit(this, std::move(handler)); });
|
1404
1453
|
if (ec) {
|
1405
1454
|
throw client_error(*ec, "after_atr_commit hook raised error");
|
1406
1455
|
}
|
@@ -1490,7 +1539,9 @@ attempt_context_impl::atr_commit_ambiguity_resolution()
|
|
1490
1539
|
if (ec) {
|
1491
1540
|
throw client_error(*ec, "atr_commit_ambiguity_resolution raised error");
|
1492
1541
|
}
|
1493
|
-
|
1542
|
+
ec =
|
1543
|
+
wait_for_hook([this](auto handler) mutable { return hooks_.before_atr_commit_ambiguity_resolution(this, std::move(handler)); });
|
1544
|
+
if (ec) {
|
1494
1545
|
throw client_error(*ec, "before_atr_commit_ambiguity_resolution hook threw error");
|
1495
1546
|
}
|
1496
1547
|
std::string prefix(ATR_FIELD_ATTEMPTS + "." + id() + ".");
|
@@ -1540,7 +1591,7 @@ attempt_context_impl::atr_complete()
|
|
1540
1591
|
{
|
1541
1592
|
try {
|
1542
1593
|
result atr_res;
|
1543
|
-
auto ec = hooks_.before_atr_complete(this);
|
1594
|
+
auto ec = wait_for_hook([this](auto handler) mutable { return hooks_.before_atr_complete(this, std::move(handler)); });
|
1544
1595
|
if (ec) {
|
1545
1596
|
throw client_error(*ec, "before_atr_complete hook threw error");
|
1546
1597
|
}
|
@@ -1562,7 +1613,7 @@ attempt_context_impl::atr_complete()
|
|
1562
1613
|
overall_.cluster_ref().execute(
|
1563
1614
|
req, [barrier](core::operations::mutate_in_response resp) { barrier->set_value(result::create_from_subdoc_response(resp)); });
|
1564
1615
|
wrap_operation_future(f);
|
1565
|
-
ec = hooks_.after_atr_complete(this);
|
1616
|
+
ec = wait_for_hook([this](auto handler) mutable { return hooks_.after_atr_complete(this, std::move(handler)); });
|
1566
1617
|
if (ec) {
|
1567
1618
|
throw client_error(*ec, "after_atr_complete hook threw error");
|
1568
1619
|
}
|
@@ -1640,7 +1691,8 @@ attempt_context_impl::atr_abort()
|
|
1640
1691
|
if (ec) {
|
1641
1692
|
throw client_error(*ec, "atr_abort check for expiry threw error");
|
1642
1693
|
}
|
1643
|
-
|
1694
|
+
ec = wait_for_hook([this](auto handler) mutable { return hooks_.before_atr_aborted(this, std::move(handler)); });
|
1695
|
+
if (ec) {
|
1644
1696
|
throw client_error(*ec, "before_atr_aborted hook threw error");
|
1645
1697
|
}
|
1646
1698
|
std::string prefix(ATR_FIELD_ATTEMPTS + "." + id() + ".");
|
@@ -1663,7 +1715,8 @@ attempt_context_impl::atr_abort()
|
|
1663
1715
|
req, [barrier](core::operations::mutate_in_response resp) { barrier->set_value(result::create_from_subdoc_response(resp)); });
|
1664
1716
|
wrap_operation_future(f);
|
1665
1717
|
state(attempt_state::ABORTED);
|
1666
|
-
|
1718
|
+
|
1719
|
+
ec = wait_for_hook([this](auto handler) mutable { return hooks_.after_atr_aborted(this, std::move(handler)); });
|
1667
1720
|
if (ec) {
|
1668
1721
|
throw client_error(*ec, "after_atr_aborted hook threw error");
|
1669
1722
|
}
|
@@ -1704,7 +1757,9 @@ attempt_context_impl::atr_rollback_complete()
|
|
1704
1757
|
if (ec) {
|
1705
1758
|
throw client_error(*ec, "atr_rollback_complete raised error");
|
1706
1759
|
}
|
1707
|
-
|
1760
|
+
|
1761
|
+
ec = wait_for_hook([this](auto handler) mutable { return hooks_.before_atr_rolled_back(this, std::move(handler)); });
|
1762
|
+
if (ec) {
|
1708
1763
|
throw client_error(*ec, "before_atr_rolled_back hook threw error");
|
1709
1764
|
}
|
1710
1765
|
std::string prefix(ATR_FIELD_ATTEMPTS + "." + id());
|
@@ -1721,7 +1776,7 @@ attempt_context_impl::atr_rollback_complete()
|
|
1721
1776
|
req, [barrier](core::operations::mutate_in_response resp) { barrier->set_value(result::create_from_subdoc_response(resp)); });
|
1722
1777
|
wrap_operation_future(f);
|
1723
1778
|
state(attempt_state::ROLLED_BACK);
|
1724
|
-
ec = hooks_.after_atr_rolled_back(this);
|
1779
|
+
ec = wait_for_hook([this](auto handler) mutable { return hooks_.after_atr_rolled_back(this, std::move(handler)); });
|
1725
1780
|
if (ec) {
|
1726
1781
|
throw client_error(*ec, "after_atr_rolled_back hook threw error");
|
1727
1782
|
}
|
@@ -1928,58 +1983,78 @@ attempt_context_impl::set_atr_pending_locked(const core::document_id& id, std::u
|
|
1928
1983
|
return fn(err);
|
1929
1984
|
}
|
1930
1985
|
};
|
1931
|
-
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
std::chrono::nanoseconds remaining = overall_.remaining();
|
1937
|
-
// This bounds the value to [0-timeout]. It should always be in this range, this is just to protect
|
1938
|
-
// against the application clock changing.
|
1939
|
-
long remaining_bounded_nanos =
|
1940
|
-
std::max(std::min(remaining.count(), overall_.config().timeout.count()), static_cast<std::chrono::nanoseconds::rep>(0));
|
1941
|
-
long remaining_bounded_msecs = remaining_bounded_nanos / 1'000'000;
|
1942
|
-
|
1943
|
-
core::operations::mutate_in_request req{ atr_id_.value() };
|
1986
|
+
return hooks_.before_atr_pending(
|
1987
|
+
this, [this, id, prefix, fn = std::forward<Handler>(fn), error_handler = std::move(error_handler)](auto ec) mutable {
|
1988
|
+
if (ec) {
|
1989
|
+
return error_handler(*ec, "before_atr_pending hook raised error", id, std::forward<Handler>(fn));
|
1990
|
+
}
|
1944
1991
|
|
1945
|
-
|
1946
|
-
|
1947
|
-
|
1948
|
-
|
1949
|
-
|
1950
|
-
|
1951
|
-
|
1952
|
-
|
1953
|
-
|
1954
|
-
|
1955
|
-
|
1956
|
-
|
1957
|
-
|
1958
|
-
|
1959
|
-
|
1960
|
-
|
1961
|
-
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1992
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(this, "updating atr {}", atr_id_.value());
|
1993
|
+
|
1994
|
+
std::chrono::nanoseconds remaining = overall_.remaining();
|
1995
|
+
// This bounds the value to [0-timeout]. It should always be in this range, this is just to protect
|
1996
|
+
// against the application clock changing.
|
1997
|
+
long remaining_bounded_nanos =
|
1998
|
+
std::max(std::min(remaining.count(), overall_.config().timeout.count()), static_cast<std::chrono::nanoseconds::rep>(0));
|
1999
|
+
long remaining_bounded_msecs = remaining_bounded_nanos / 1'000'000;
|
2000
|
+
|
2001
|
+
core::operations::mutate_in_request req{ atr_id_.value() };
|
2002
|
+
|
2003
|
+
req.specs =
|
2004
|
+
couchbase::mutate_in_specs{
|
2005
|
+
couchbase::mutate_in_specs::insert(prefix + ATR_FIELD_TRANSACTION_ID, overall_.transaction_id())
|
2006
|
+
.xattr()
|
2007
|
+
.create_path(),
|
2008
|
+
couchbase::mutate_in_specs::insert(prefix + ATR_FIELD_STATUS, attempt_state_name(attempt_state::PENDING))
|
2009
|
+
.xattr()
|
2010
|
+
.create_path(),
|
2011
|
+
couchbase::mutate_in_specs::insert(prefix + ATR_FIELD_START_TIMESTAMP, subdoc::mutate_in_macro::cas)
|
2012
|
+
.xattr()
|
2013
|
+
.create_path(),
|
2014
|
+
couchbase::mutate_in_specs::insert(prefix + ATR_FIELD_EXPIRES_AFTER_MSECS, remaining_bounded_msecs)
|
2015
|
+
.xattr()
|
2016
|
+
.create_path(),
|
2017
|
+
// ExtStoreDurability
|
2018
|
+
couchbase::mutate_in_specs::insert(prefix + ATR_FIELD_DURABILITY_LEVEL,
|
2019
|
+
store_durability_level_to_string(overall_.config().level))
|
2020
|
+
.xattr()
|
2021
|
+
.create_path(),
|
2022
|
+
// subdoc::opcode::set_doc used in replace w/ empty path
|
2023
|
+
// ExtBinaryMetadata
|
2024
|
+
couchbase::mutate_in_specs::replace_raw({}, std::vector<std::byte>{ std::byte{ 0x00 } }),
|
2025
|
+
}
|
2026
|
+
.specs();
|
2027
|
+
req.store_semantics = couchbase::store_semantics::upsert;
|
2028
|
+
|
2029
|
+
wrap_durable_request(req, overall_.config());
|
2030
|
+
return overall_.cluster_ref().execute(
|
2031
|
+
req,
|
2032
|
+
[this, fn = std::forward<Handler>(fn), error_handler = std::move(error_handler)](
|
2033
|
+
core::operations::mutate_in_response resp) mutable {
|
2034
|
+
auto ec = error_class_from_response(resp);
|
2035
|
+
if (ec) {
|
2036
|
+
return error_handler(*ec,
|
2037
|
+
resp.ctx.ec().message(),
|
2038
|
+
{ resp.ctx.bucket(), resp.ctx.scope(), resp.ctx.collection(), resp.ctx.id() },
|
2039
|
+
std::forward<Handler>(fn));
|
2040
|
+
}
|
2041
|
+
return hooks_.after_atr_pending(
|
2042
|
+
this,
|
2043
|
+
[this, fn = std::forward<Handler>(fn), error_handler = std::move(error_handler), resp = std::move(resp)](
|
2044
|
+
auto ec) mutable {
|
2045
|
+
if (ec) {
|
2046
|
+
return error_handler(*ec,
|
2047
|
+
fmt::format("after_atr_pending returned hook raised {}", *ec),
|
2048
|
+
{ resp.ctx.bucket(), resp.ctx.scope(), resp.ctx.collection(), resp.ctx.id() },
|
2049
|
+
std::forward<Handler>(fn));
|
2050
|
+
}
|
1966
2051
|
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
}
|
1974
|
-
if (!ec) {
|
1975
|
-
state(attempt_state::PENDING);
|
1976
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(this, "set ATR {} to Pending, got CAS (start time) {}", atr_id_.value(), resp.cas.value());
|
1977
|
-
return fn(std::nullopt);
|
1978
|
-
}
|
1979
|
-
return error_handler(*ec,
|
1980
|
-
resp.ctx.ec().message(),
|
1981
|
-
{ resp.ctx.bucket(), resp.ctx.scope(), resp.ctx.collection(), resp.ctx.id() },
|
1982
|
-
std::forward<Handler>(fn));
|
2052
|
+
state(attempt_state::PENDING);
|
2053
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(
|
2054
|
+
this, "set ATR {} to Pending, got CAS (start time) {}", atr_id_.value(), resp.cas.value());
|
2055
|
+
return fn(std::nullopt);
|
2056
|
+
});
|
2057
|
+
});
|
1983
2058
|
});
|
1984
2059
|
}
|
1985
2060
|
} catch (const std::exception& e) {
|
@@ -2034,113 +2109,123 @@ attempt_context_impl::do_get(const core::document_id& id, const std::optional<st
|
|
2034
2109
|
return cb(FAIL_DOC_NOT_FOUND, msg, std::nullopt);
|
2035
2110
|
}
|
2036
2111
|
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2043
|
-
[this, id, resolving_missing_atr_entry = std::move(resolving_missing_atr_entry), cb = std::move(cb)](
|
2044
|
-
std::optional<error_class> ec, std::optional<std::string> err_message, std::optional<transaction_get_result> doc) mutable {
|
2045
|
-
if (!ec && !doc) {
|
2046
|
-
// it just isn't there.
|
2047
|
-
return cb(std::nullopt, std::nullopt, std::nullopt);
|
2112
|
+
return hooks_.before_doc_get(
|
2113
|
+
this,
|
2114
|
+
id.key(),
|
2115
|
+
[this, id, resolving_missing_atr_entry = std::move(resolving_missing_atr_entry), cb = std::move(cb)](auto ec) mutable {
|
2116
|
+
if (ec) {
|
2117
|
+
return cb(ec, "before_doc_get hook raised error", std::nullopt);
|
2048
2118
|
}
|
2049
|
-
if (!ec) {
|
2050
|
-
if (doc->links().is_document_in_transaction()) {
|
2051
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(
|
2052
|
-
this, "doc {} in transaction, resolving_missing_atr_entry={}", *doc, resolving_missing_atr_entry.value_or("-"));
|
2053
|
-
|
2054
|
-
if (resolving_missing_atr_entry.has_value() &&
|
2055
|
-
resolving_missing_atr_entry.value() == doc->links().staged_attempt_id()) {
|
2056
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(this, "doc is in lost pending transaction");
|
2057
|
-
|
2058
|
-
if (doc->links().is_document_being_inserted()) {
|
2059
|
-
// this document is being inserted, so should not be visible yet
|
2060
|
-
return cb(std::nullopt, std::nullopt, std::nullopt);
|
2061
|
-
}
|
2062
2119
|
|
2063
|
-
|
2064
|
-
|
2065
|
-
|
2066
|
-
|
2067
|
-
|
2068
|
-
|
2069
|
-
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2073
|
-
|
2074
|
-
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
2085
|
-
|
2086
|
-
if (doc->links().staged_attempt_id() && entry->attempt_id() == this->id()) {
|
2087
|
-
// Attempt is reading its own writes
|
2088
|
-
// This is here as backup, it should be returned from the in-memory cache instead
|
2089
|
-
content = doc->links().staged_content();
|
2090
|
-
} else {
|
2091
|
-
auto err = forward_compat::check(forward_compat_stage::GETS_READING_ATR, entry->forward_compat());
|
2092
|
-
if (err) {
|
2093
|
-
return cb(FAIL_OTHER, err->what(), std::nullopt);
|
2094
|
-
}
|
2095
|
-
switch (entry->state()) {
|
2096
|
-
case attempt_state::COMPLETED:
|
2097
|
-
case attempt_state::COMMITTED:
|
2098
|
-
if (doc->links().is_document_being_removed()) {
|
2099
|
-
ignore_doc = true;
|
2100
|
-
} else {
|
2101
|
-
content = doc->links().staged_content();
|
2102
|
-
}
|
2103
|
-
break;
|
2104
|
-
default:
|
2105
|
-
if (doc->links().is_document_being_inserted()) {
|
2106
|
-
// This document is being inserted, so should not be visible yet
|
2107
|
-
ignore_doc = true;
|
2108
|
-
}
|
2109
|
-
break;
|
2110
|
-
}
|
2111
|
-
}
|
2112
|
-
} else {
|
2113
|
-
// failed to get the ATR entry
|
2114
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(this,
|
2115
|
-
"could not get ATR entry, checking again with {}",
|
2116
|
-
doc->links().staged_attempt_id().value_or("-"));
|
2117
|
-
return do_get(id, doc->links().staged_attempt_id(), cb);
|
2118
|
-
}
|
2119
|
-
if (ignore_doc) {
|
2120
|
+
return get_doc(
|
2121
|
+
id,
|
2122
|
+
[this, id, resolving_missing_atr_entry = std::move(resolving_missing_atr_entry), cb = std::move(cb)](
|
2123
|
+
std::optional<error_class> ec,
|
2124
|
+
std::optional<std::string> err_message,
|
2125
|
+
std::optional<transaction_get_result> doc) mutable {
|
2126
|
+
if (!ec && !doc) {
|
2127
|
+
// it just isn't there.
|
2128
|
+
return cb(std::nullopt, std::nullopt, std::nullopt);
|
2129
|
+
}
|
2130
|
+
if (!ec) {
|
2131
|
+
if (doc->links().is_document_in_transaction()) {
|
2132
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(this,
|
2133
|
+
"doc {} in transaction, resolving_missing_atr_entry={}",
|
2134
|
+
*doc,
|
2135
|
+
resolving_missing_atr_entry.value_or("-"));
|
2136
|
+
|
2137
|
+
if (resolving_missing_atr_entry.has_value() &&
|
2138
|
+
resolving_missing_atr_entry.value() == doc->links().staged_attempt_id()) {
|
2139
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(this, "doc is in lost pending transaction");
|
2140
|
+
|
2141
|
+
if (doc->links().is_document_being_inserted()) {
|
2142
|
+
// this document is being inserted, so should not be visible yet
|
2120
2143
|
return cb(std::nullopt, std::nullopt, std::nullopt);
|
2121
2144
|
}
|
2122
|
-
return cb(std::nullopt, std::nullopt, transaction_get_result::create_from(*doc, content));
|
2123
2145
|
|
2124
|
-
|
2125
|
-
// failed to get the ATR
|
2126
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(
|
2127
|
-
this, "could not get ATR, checking again with {}", doc->links().staged_attempt_id().value_or("-"));
|
2128
|
-
return do_get(id, doc->links().staged_attempt_id(), cb);
|
2146
|
+
return cb(std::nullopt, std::nullopt, doc);
|
2129
2147
|
}
|
2130
|
-
});
|
2131
|
-
} else {
|
2132
|
-
if (doc->links().is_deleted()) {
|
2133
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(this, "doc not in txn, and is_deleted, so not returning it.");
|
2134
|
-
// doc has been deleted, not in txn, so don't return it
|
2135
|
-
return cb(std::nullopt, std::nullopt, std::nullopt);
|
2136
|
-
}
|
2137
|
-
return cb(std::nullopt, std::nullopt, doc);
|
2138
|
-
}
|
2139
|
-
} else {
|
2140
|
-
return cb(ec, err_message, std::nullopt);
|
2141
|
-
}
|
2142
|
-
});
|
2143
2148
|
|
2149
|
+
core::document_id doc_atr_id{ doc->links().atr_bucket_name().value(),
|
2150
|
+
doc->links().atr_scope_name().value(),
|
2151
|
+
doc->links().atr_collection_name().value(),
|
2152
|
+
doc->links().atr_id().value() };
|
2153
|
+
active_transaction_record::get_atr(
|
2154
|
+
cluster_ref(),
|
2155
|
+
doc_atr_id,
|
2156
|
+
[this, id, doc, cb = std::move(cb)](std::error_code ec2,
|
2157
|
+
std::optional<active_transaction_record> atr) mutable {
|
2158
|
+
if (!ec2 && atr) {
|
2159
|
+
active_transaction_record& atr_doc = atr.value();
|
2160
|
+
std::optional<atr_entry> entry;
|
2161
|
+
for (const auto& e : atr_doc.entries()) {
|
2162
|
+
if (doc->links().staged_attempt_id().value() == e.attempt_id()) {
|
2163
|
+
entry.emplace(e);
|
2164
|
+
break;
|
2165
|
+
}
|
2166
|
+
}
|
2167
|
+
bool ignore_doc = false;
|
2168
|
+
auto content = doc->content();
|
2169
|
+
if (entry) {
|
2170
|
+
if (doc->links().staged_attempt_id() && entry->attempt_id() == this->id()) {
|
2171
|
+
// Attempt is reading its own writes
|
2172
|
+
// This is here as backup, it should be returned from the in-memory cache instead
|
2173
|
+
content = doc->links().staged_content();
|
2174
|
+
} else {
|
2175
|
+
auto err =
|
2176
|
+
forward_compat::check(forward_compat_stage::GETS_READING_ATR, entry->forward_compat());
|
2177
|
+
if (err) {
|
2178
|
+
return cb(FAIL_OTHER, err->what(), std::nullopt);
|
2179
|
+
}
|
2180
|
+
switch (entry->state()) {
|
2181
|
+
case attempt_state::COMPLETED:
|
2182
|
+
case attempt_state::COMMITTED:
|
2183
|
+
if (doc->links().is_document_being_removed()) {
|
2184
|
+
ignore_doc = true;
|
2185
|
+
} else {
|
2186
|
+
content = doc->links().staged_content();
|
2187
|
+
}
|
2188
|
+
break;
|
2189
|
+
default:
|
2190
|
+
if (doc->links().is_document_being_inserted()) {
|
2191
|
+
// This document is being inserted, so should not be visible yet
|
2192
|
+
ignore_doc = true;
|
2193
|
+
}
|
2194
|
+
break;
|
2195
|
+
}
|
2196
|
+
}
|
2197
|
+
} else {
|
2198
|
+
// failed to get the ATR entry
|
2199
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(this,
|
2200
|
+
"could not get ATR entry, checking again with {}",
|
2201
|
+
doc->links().staged_attempt_id().value_or("-"));
|
2202
|
+
return do_get(id, doc->links().staged_attempt_id(), cb);
|
2203
|
+
}
|
2204
|
+
if (ignore_doc) {
|
2205
|
+
return cb(std::nullopt, std::nullopt, std::nullopt);
|
2206
|
+
}
|
2207
|
+
return cb(std::nullopt, std::nullopt, transaction_get_result::create_from(*doc, content));
|
2208
|
+
|
2209
|
+
} else {
|
2210
|
+
// failed to get the ATR
|
2211
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(
|
2212
|
+
this, "could not get ATR, checking again with {}", doc->links().staged_attempt_id().value_or("-"));
|
2213
|
+
return do_get(id, doc->links().staged_attempt_id(), cb);
|
2214
|
+
}
|
2215
|
+
});
|
2216
|
+
} else {
|
2217
|
+
if (doc->links().is_deleted()) {
|
2218
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(this, "doc not in txn, and is_deleted, so not returning it.");
|
2219
|
+
// doc has been deleted, not in txn, so don't return it
|
2220
|
+
return cb(std::nullopt, std::nullopt, std::nullopt);
|
2221
|
+
}
|
2222
|
+
return cb(std::nullopt, std::nullopt, doc);
|
2223
|
+
}
|
2224
|
+
} else {
|
2225
|
+
return cb(ec, err_message, std::nullopt);
|
2226
|
+
}
|
2227
|
+
});
|
2228
|
+
});
|
2144
2229
|
} catch (const transaction_operation_failed&) {
|
2145
2230
|
throw;
|
2146
2231
|
} catch (const std::exception& ex) {
|
@@ -2250,88 +2335,96 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
|
|
2250
2335
|
fmt::format("failed getting doc in create_staged_insert with {}", err_message)));
|
2251
2336
|
}
|
2252
2337
|
};
|
2253
|
-
|
2254
|
-
|
2255
|
-
|
2256
|
-
|
2257
|
-
|
2258
|
-
id,
|
2259
|
-
[this, id, content, op_id, cb = std::forward<Handler>(cb), error_handler, delay](
|
2260
|
-
std::optional<error_class> ec3, std::optional<std::string> err_message, std::optional<transaction_get_result> doc) mutable {
|
2261
|
-
if (!ec3) {
|
2262
|
-
if (doc) {
|
2263
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(this,
|
2264
|
-
"document {} exists, is_in_transaction {}, is_deleted {} ",
|
2265
|
-
doc->id(),
|
2266
|
-
doc->links().is_document_in_transaction(),
|
2267
|
-
doc->links().is_deleted());
|
2268
|
-
|
2269
|
-
if (auto err = forward_compat::check(forward_compat_stage::WWC_INSERTING_GET, doc->links().forward_compat());
|
2270
|
-
err) {
|
2271
|
-
return op_completed_with_error(std::forward<Handler>(cb), *err);
|
2272
|
-
}
|
2273
|
-
if (!doc->links().is_document_in_transaction() && doc->links().is_deleted()) {
|
2274
|
-
// it is just a deleted doc, so we are ok. Let's try again, but with the cas
|
2275
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(
|
2276
|
-
this, "create staged insert found existing deleted doc, retrying with cas {}", doc->cas().value());
|
2277
|
-
delay();
|
2278
|
-
return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
|
2279
|
-
}
|
2280
|
-
if (!doc->links().is_document_in_transaction()) {
|
2281
|
-
// doc was inserted outside txn elsewhere
|
2282
|
-
CB_ATTEMPT_CTX_LOG_TRACE(this, "doc {} not in txn - was inserted outside txn", id);
|
2283
|
-
return op_completed_with_error(
|
2284
|
-
std::forward<Handler>(cb),
|
2285
|
-
document_exists({ couchbase::errc::transaction_op::document_exists_exception, key_value_error_context() }));
|
2286
|
-
}
|
2287
|
-
if (doc->links().staged_attempt_id() == this->id()) {
|
2288
|
-
if (doc->links().staged_operation_id() == op_id) {
|
2289
|
-
// this is us dealing with resolving an ambiguity. So, lets just update the staged_mutation with the
|
2290
|
-
// correct cas and continue...
|
2291
|
-
staged_mutations_->add(staged_mutation(*doc, content, staged_mutation_type::INSERT));
|
2292
|
-
return op_completed_with_callback(std::forward<Handler>(cb), doc);
|
2293
|
-
}
|
2294
|
-
return op_completed_with_error(
|
2295
|
-
std::forward<Handler>(cb),
|
2296
|
-
transaction_operation_failed(FAIL_OTHER, "concurrent operations on a document are not allowed")
|
2297
|
-
.cause(CONCURRENT_OPERATIONS_DETECTED_ON_SAME_DOCUMENT));
|
2298
|
-
}
|
2299
|
-
// CBD-3787 - Only a staged insert is ok to overwrite
|
2300
|
-
if (doc->links().op() && *doc->links().op() != "insert") {
|
2301
|
-
return op_completed_with_error(
|
2302
|
-
std::forward<Handler>(cb),
|
2303
|
-
transaction_operation_failed(FAIL_DOC_ALREADY_EXISTS, "doc exists, not a staged insert")
|
2304
|
-
.cause(DOCUMENT_EXISTS_EXCEPTION));
|
2305
|
-
}
|
2306
|
-
check_and_handle_blocking_transactions(
|
2307
|
-
*doc,
|
2308
|
-
forward_compat_stage::WWC_INSERTING,
|
2309
|
-
[this, id, op_id, content, doc, cb = std::forward<Handler>(cb), delay](
|
2310
|
-
std::optional<transaction_operation_failed> err) mutable {
|
2311
|
-
if (err) {
|
2312
|
-
return op_completed_with_error(std::move(cb), *err);
|
2313
|
-
}
|
2314
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(
|
2315
|
-
this, "doc ok to overwrite, retrying create_staged_insert with cas {}", doc->cas().value());
|
2316
|
-
delay();
|
2317
|
-
return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
|
2318
|
-
});
|
2319
|
-
} else {
|
2320
|
-
// no doc now, just retry entire txn
|
2321
|
-
CB_ATTEMPT_CTX_LOG_TRACE(this, "got {} from get_doc in exists during staged insert", *ec3);
|
2322
|
-
return op_completed_with_error(
|
2323
|
-
std::move(cb),
|
2324
|
-
transaction_operation_failed(FAIL_DOC_NOT_FOUND, "insert failed as the doc existed, but now seems to not exist")
|
2325
|
-
.retry());
|
2326
|
-
}
|
2327
|
-
} else {
|
2328
|
-
return error_handler(*ec3, *err_message, std::forward<Handler>(cb));
|
2338
|
+
return hooks_.before_get_doc_in_exists_during_staged_insert(
|
2339
|
+
this, id.key(), [this, id, content, op_id, cb = std::forward<Handler>(cb), error_handler, delay](auto ec) mutable {
|
2340
|
+
if (ec) {
|
2341
|
+
return error_handler(
|
2342
|
+
*ec, fmt::format("before_get_doc_in_exists_during_staged_insert hook raised {}", *ec), std::forward<Handler>(cb));
|
2329
2343
|
}
|
2344
|
+
return get_doc(
|
2345
|
+
id,
|
2346
|
+
[this, id, content, op_id, cb = std::forward<Handler>(cb), error_handler, delay](
|
2347
|
+
std::optional<error_class> ec3,
|
2348
|
+
std::optional<std::string> err_message,
|
2349
|
+
std::optional<transaction_get_result> doc) mutable {
|
2350
|
+
if (!ec3) {
|
2351
|
+
if (doc) {
|
2352
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(this,
|
2353
|
+
"document {} exists, is_in_transaction {}, is_deleted {} ",
|
2354
|
+
doc->id(),
|
2355
|
+
doc->links().is_document_in_transaction(),
|
2356
|
+
doc->links().is_deleted());
|
2357
|
+
|
2358
|
+
if (auto err =
|
2359
|
+
forward_compat::check(forward_compat_stage::WWC_INSERTING_GET, doc->links().forward_compat());
|
2360
|
+
err) {
|
2361
|
+
return op_completed_with_error(std::forward<Handler>(cb), *err);
|
2362
|
+
}
|
2363
|
+
if (!doc->links().is_document_in_transaction() && doc->links().is_deleted()) {
|
2364
|
+
// it is just a deleted doc, so we are ok. Let's try again, but with the cas
|
2365
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(
|
2366
|
+
this, "create staged insert found existing deleted doc, retrying with cas {}", doc->cas().value());
|
2367
|
+
delay();
|
2368
|
+
return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
|
2369
|
+
}
|
2370
|
+
if (!doc->links().is_document_in_transaction()) {
|
2371
|
+
// doc was inserted outside txn elsewhere
|
2372
|
+
CB_ATTEMPT_CTX_LOG_TRACE(this, "doc {} not in txn - was inserted outside txn", id);
|
2373
|
+
return op_completed_with_error(
|
2374
|
+
std::forward<Handler>(cb),
|
2375
|
+
document_exists(
|
2376
|
+
{ couchbase::errc::transaction_op::document_exists_exception, key_value_error_context() }));
|
2377
|
+
}
|
2378
|
+
if (doc->links().staged_attempt_id() == this->id()) {
|
2379
|
+
if (doc->links().staged_operation_id() == op_id) {
|
2380
|
+
// this is us dealing with resolving an ambiguity. So, lets just update the staged_mutation with
|
2381
|
+
// the correct cas and continue...
|
2382
|
+
staged_mutations_->add(staged_mutation(*doc, content, staged_mutation_type::INSERT));
|
2383
|
+
return op_completed_with_callback(std::forward<Handler>(cb), doc);
|
2384
|
+
}
|
2385
|
+
return op_completed_with_error(
|
2386
|
+
std::forward<Handler>(cb),
|
2387
|
+
transaction_operation_failed(FAIL_OTHER, "concurrent operations on a document are not allowed")
|
2388
|
+
.cause(CONCURRENT_OPERATIONS_DETECTED_ON_SAME_DOCUMENT));
|
2389
|
+
}
|
2390
|
+
// CBD-3787 - Only a staged insert is ok to overwrite
|
2391
|
+
if (doc->links().op() && *doc->links().op() != "insert") {
|
2392
|
+
return op_completed_with_error(
|
2393
|
+
std::forward<Handler>(cb),
|
2394
|
+
transaction_operation_failed(FAIL_DOC_ALREADY_EXISTS, "doc exists, not a staged insert")
|
2395
|
+
.cause(DOCUMENT_EXISTS_EXCEPTION));
|
2396
|
+
}
|
2397
|
+
check_and_handle_blocking_transactions(
|
2398
|
+
*doc,
|
2399
|
+
forward_compat_stage::WWC_INSERTING,
|
2400
|
+
[this, id, op_id, content, doc, cb = std::forward<Handler>(cb), delay](
|
2401
|
+
std::optional<transaction_operation_failed> err) mutable {
|
2402
|
+
if (err) {
|
2403
|
+
return op_completed_with_error(std::move(cb), *err);
|
2404
|
+
}
|
2405
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(
|
2406
|
+
this, "doc ok to overwrite, retrying create_staged_insert with cas {}", doc->cas().value());
|
2407
|
+
delay();
|
2408
|
+
return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
|
2409
|
+
});
|
2410
|
+
} else {
|
2411
|
+
// no doc now, just retry entire txn
|
2412
|
+
CB_ATTEMPT_CTX_LOG_TRACE(this, "got {} from get_doc in exists during staged insert", *ec3);
|
2413
|
+
return op_completed_with_error(
|
2414
|
+
std::move(cb),
|
2415
|
+
transaction_operation_failed(FAIL_DOC_NOT_FOUND,
|
2416
|
+
"insert failed as the doc existed, but now seems to not exist")
|
2417
|
+
.retry());
|
2418
|
+
}
|
2419
|
+
} else {
|
2420
|
+
return error_handler(*ec3, *err_message, std::forward<Handler>(cb));
|
2421
|
+
}
|
2422
|
+
});
|
2330
2423
|
});
|
2331
|
-
break;
|
2332
2424
|
}
|
2333
2425
|
default:
|
2334
|
-
return op_completed_with_error(std::
|
2426
|
+
return op_completed_with_error(std::forward<Handler>(cb),
|
2427
|
+
transaction_operation_failed(ec, "failed in create_staged_insert").retry());
|
2335
2428
|
}
|
2336
2429
|
}
|
2337
2430
|
|
@@ -2356,7 +2449,9 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
|
|
2356
2449
|
"create_staged_insert expired and not in overtime");
|
2357
2450
|
}
|
2358
2451
|
|
2359
|
-
|
2452
|
+
auto ec =
|
2453
|
+
wait_for_hook([this, key = id.key()](auto handler) mutable { return hooks_.before_staged_insert(this, key, std::move(handler)); });
|
2454
|
+
if (ec) {
|
2360
2455
|
return create_staged_insert_error_handler(
|
2361
2456
|
id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, "before_staged_insert hook threw error");
|
2362
2457
|
}
|
@@ -2371,43 +2466,43 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
|
|
2371
2466
|
req,
|
2372
2467
|
[this, id, content, cas, op_id, cb = std::forward<Handler>(cb), delay = std::forward<Delay>(delay)](
|
2373
2468
|
core::operations::mutate_in_response resp) mutable {
|
2374
|
-
auto ec =
|
2375
|
-
if (ec) {
|
2376
|
-
auto msg = (resp.ctx.ec() ? resp.ctx.ec().message() : "after_staged_insert hook threw error");
|
2469
|
+
if (auto ec = error_class_from_response(resp); ec) {
|
2377
2470
|
return create_staged_insert_error_handler(
|
2378
|
-
id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec,
|
2379
|
-
}
|
2380
|
-
if (!resp.ctx.ec()) {
|
2381
|
-
CB_ATTEMPT_CTX_LOG_DEBUG(this, "inserted doc {} CAS={}, {}", id, resp.cas.value(), resp.ctx.ec().message());
|
2382
|
-
|
2383
|
-
// TODO: clean this up (do most of this in transactions_document(...))
|
2384
|
-
transaction_links links(atr_id_->key(),
|
2385
|
-
id.bucket(),
|
2386
|
-
id.scope(),
|
2387
|
-
id.collection(),
|
2388
|
-
overall_.transaction_id(),
|
2389
|
-
this->id(),
|
2390
|
-
op_id,
|
2391
|
-
content,
|
2392
|
-
std::nullopt,
|
2393
|
-
std::nullopt,
|
2394
|
-
std::nullopt,
|
2395
|
-
std::nullopt,
|
2396
|
-
std::string("insert"),
|
2397
|
-
std::nullopt,
|
2398
|
-
true);
|
2399
|
-
transaction_get_result out(id, content, resp.cas.value(), links, std::nullopt);
|
2400
|
-
staged_mutations_->add(staged_mutation(out, content, staged_mutation_type::INSERT));
|
2401
|
-
return op_completed_with_callback(cb, std::optional<transaction_get_result>(out));
|
2471
|
+
id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, resp.ctx.ec().message());
|
2402
2472
|
}
|
2403
|
-
return
|
2404
|
-
|
2405
|
-
|
2406
|
-
|
2407
|
-
|
2408
|
-
|
2409
|
-
|
2410
|
-
|
2473
|
+
return hooks_.after_staged_insert_complete(
|
2474
|
+
this,
|
2475
|
+
id.key(),
|
2476
|
+
[this, id, content, cas, op_id, cb = std::forward<Handler>(cb), delay = std::forward<Delay>(delay), resp = std::move(resp)](
|
2477
|
+
auto ec) mutable {
|
2478
|
+
if (ec) {
|
2479
|
+
auto msg = (resp.ctx.ec() ? resp.ctx.ec().message() : "after_staged_insert hook threw error");
|
2480
|
+
return create_staged_insert_error_handler(
|
2481
|
+
id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, msg);
|
2482
|
+
}
|
2483
|
+
|
2484
|
+
CB_ATTEMPT_CTX_LOG_DEBUG(this, "inserted doc {} CAS={}, {}", id, resp.cas.value(), resp.ctx.ec().message());
|
2485
|
+
|
2486
|
+
// TODO: clean this up (do most of this in transactions_document(...))
|
2487
|
+
transaction_links links(atr_id_->key(),
|
2488
|
+
id.bucket(),
|
2489
|
+
id.scope(),
|
2490
|
+
id.collection(),
|
2491
|
+
overall_.transaction_id(),
|
2492
|
+
this->id(),
|
2493
|
+
op_id,
|
2494
|
+
content,
|
2495
|
+
std::nullopt,
|
2496
|
+
std::nullopt,
|
2497
|
+
std::nullopt,
|
2498
|
+
std::nullopt,
|
2499
|
+
std::string("insert"),
|
2500
|
+
std::nullopt,
|
2501
|
+
true);
|
2502
|
+
transaction_get_result out(id, content, resp.cas.value(), links, std::nullopt);
|
2503
|
+
staged_mutations_->add(staged_mutation(out, content, staged_mutation_type::INSERT));
|
2504
|
+
return op_completed_with_callback(cb, std::optional<transaction_get_result>(out));
|
2505
|
+
});
|
2411
2506
|
});
|
2412
2507
|
}
|
2413
2508
|
|