couchbase 4.2.11 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. package/deps/couchbase-cxx-cache/mozilla-ca-bundle.crt +49 -2
  2. package/deps/couchbase-cxx-cache/mozilla-ca-bundle.sha256 +1 -1
  3. package/deps/couchbase-cxx-client/core/impl/cluster.cxx +51 -5
  4. package/deps/couchbase-cxx-client/core/impl/collection.cxx +224 -209
  5. package/deps/couchbase-cxx-client/core/impl/query_error_context.cxx +2 -2
  6. package/deps/couchbase-cxx-client/core/impl/query_index_manager.cxx +1 -0
  7. package/deps/couchbase-cxx-client/core/io/dns_client.cxx +4 -0
  8. package/deps/couchbase-cxx-client/core/io/dns_config.cxx +15 -4
  9. package/deps/couchbase-cxx-client/core/io/dns_config.hxx +1 -1
  10. package/deps/couchbase-cxx-client/core/io/mcbp_session.cxx +84 -53
  11. package/deps/couchbase-cxx-client/core/mcbp/operation_queue.cxx +1 -0
  12. package/deps/couchbase-cxx-client/core/meta/features.hxx +5 -0
  13. package/deps/couchbase-cxx-client/core/operations/document_lookup_in_all_replicas.hxx +116 -105
  14. package/deps/couchbase-cxx-client/core/operations/document_lookup_in_any_replica.hxx +116 -108
  15. package/deps/couchbase-cxx-client/core/operations/document_search.cxx +97 -81
  16. package/deps/couchbase-cxx-client/core/operations/document_search.hxx +5 -0
  17. package/deps/couchbase-cxx-client/core/range_scan_orchestrator_options.hxx +2 -1
  18. package/deps/couchbase-cxx-client/core/transactions/atr_cleanup_entry.cxx +16 -7
  19. package/deps/couchbase-cxx-client/core/transactions/attempt_context_impl.cxx +578 -483
  20. package/deps/couchbase-cxx-client/core/transactions/attempt_context_testing_hooks.cxx +51 -50
  21. package/deps/couchbase-cxx-client/core/transactions/attempt_context_testing_hooks.hxx +4 -2
  22. package/deps/couchbase-cxx-client/core/transactions/cleanup_testing_hooks.cxx +6 -6
  23. package/deps/couchbase-cxx-client/core/transactions/cleanup_testing_hooks.hxx +3 -2
  24. package/deps/couchbase-cxx-client/core/transactions/internal/transactions_cleanup.hxx +2 -0
  25. package/deps/couchbase-cxx-client/core/transactions/internal/utils.hxx +5 -1
  26. package/deps/couchbase-cxx-client/core/transactions/staged_mutation.cxx +222 -179
  27. package/deps/couchbase-cxx-client/core/transactions/staged_mutation.hxx +23 -12
  28. package/deps/couchbase-cxx-client/core/transactions/transactions.cxx +61 -24
  29. package/deps/couchbase-cxx-client/core/transactions/transactions_cleanup.cxx +36 -16
  30. package/deps/couchbase-cxx-client/core/transactions/utils.cxx +9 -0
  31. package/deps/couchbase-cxx-client/core/transactions.hxx +40 -7
  32. package/deps/couchbase-cxx-client/couchbase/cluster.hxx +19 -0
  33. package/deps/couchbase-cxx-client/couchbase/fork_event.hxx +39 -0
  34. package/dist/binding.d.ts +8 -0
  35. package/dist/bindingutilities.d.ts +6 -1
  36. package/dist/bindingutilities.js +15 -1
  37. package/dist/bucketmanager.d.ts +0 -12
  38. package/dist/cluster.d.ts +0 -2
  39. package/dist/cluster.js +0 -2
  40. package/dist/collection.d.ts +3 -3
  41. package/dist/collection.js +3 -1
  42. package/dist/querytypes.d.ts +0 -2
  43. package/dist/rangeScan.d.ts +0 -8
  44. package/dist/rangeScan.js +0 -8
  45. package/dist/scope.d.ts +0 -5
  46. package/dist/scope.js +0 -5
  47. package/dist/scopesearchindexmanager.d.ts +0 -2
  48. package/dist/scopesearchindexmanager.js +0 -2
  49. package/dist/searchexecutor.js +3 -1
  50. package/dist/searchtypes.d.ts +16 -6
  51. package/dist/searchtypes.js +2 -6
  52. package/dist/transactions.d.ts +23 -0
  53. package/dist/transactions.js +16 -10
  54. package/dist/vectorsearch.d.ts +8 -8
  55. package/dist/vectorsearch.js +7 -7
  56. package/package.json +7 -7
  57. package/src/instance.cpp +11 -1
  58. package/src/instance.hpp +1 -0
  59. package/src/jstocbpp_autogen.hpp +8 -0
  60. package/src/jstocbpp_transactions.hpp +40 -3
  61. package/src/transactions.cpp +12 -1
  62. 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
- id,
122
- std::nullopt,
123
- [this, id, cb = std::move(cb)](
124
- std::optional<error_class> ec, std::optional<std::string> err_message, std::optional<transaction_get_result> res) mutable {
125
- if (!ec) {
126
- ec = hooks_.after_get_complete(this, id.key());
127
- }
128
- if (ec) {
129
- switch (*ec) {
130
- case FAIL_EXPIRY:
131
- return op_completed_with_error(std::move(cb),
132
- transaction_operation_failed(*ec, "transaction expired during get").expired());
133
- case FAIL_DOC_NOT_FOUND:
134
- return op_completed_with_error(
135
- std::move(cb),
136
- transaction_operation_failed(*ec, fmt::format("document not found {}", err_message.value_or("")))
137
- .cause(external_exception::DOCUMENT_NOT_FOUND_EXCEPTION));
138
- case FAIL_TRANSIENT:
139
- return op_completed_with_error(
140
- std::move(cb),
141
- transaction_operation_failed(*ec, fmt::format("transient failure in get {}", err_message.value_or("")))
142
- .retry());
143
- case FAIL_HARD:
144
- return op_completed_with_error(
145
- std::move(cb),
146
- transaction_operation_failed(*ec, fmt::format("fail hard in get {}", err_message.value_or(""))).no_rollback());
147
- default: {
148
- auto msg = fmt::format("got error \"{}\" while getting doc {}", err_message.value_or(""), id.key());
149
- return op_completed_with_error(std::move(cb), transaction_operation_failed(FAIL_OTHER, msg));
150
- }
151
- }
152
- } else {
153
- if (!res) {
154
- return op_completed_with_error(std::move(cb), transaction_operation_failed(*ec, "document not found"));
155
- }
156
- auto err = forward_compat::check(forward_compat_stage::GETS, res->links().forward_compat());
157
- if (err) {
158
- return op_completed_with_error(std::move(cb), *err);
159
- }
160
- return op_completed_with_callback(std::move(cb), res);
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(id,
194
- std::nullopt,
195
- [this, id, cb = std::move(cb)](std::optional<error_class> ec,
196
- std::optional<std::string> err_message,
197
- std::optional<transaction_get_result> res) mutable {
198
- if (!ec) {
199
- ec = hooks_.after_get_complete(this, id.key());
200
- }
201
- if (ec) {
202
- switch (*ec) {
203
- case FAIL_EXPIRY:
204
- return op_completed_with_error(
205
- std::move(cb),
206
- transaction_operation_failed(
207
- *ec, fmt::format("transaction expired during get {}", err_message.value_or("")))
208
- .expired());
209
- case FAIL_DOC_NOT_FOUND:
210
- return op_completed_with_callback(std::move(cb), std::optional<transaction_get_result>());
211
- case FAIL_TRANSIENT:
212
- return op_completed_with_error(
213
- std::move(cb),
214
- transaction_operation_failed(*ec, fmt::format("transient failure in get {}", err_message.value_or("")))
215
- .retry());
216
- case FAIL_HARD:
217
- return op_completed_with_error(
218
- std::move(cb),
219
- transaction_operation_failed(*ec, fmt::format("fail hard in get {}", err_message.value_or("")))
220
- .no_rollback());
221
- default: {
222
- return op_completed_with_error(
223
- std::move(cb),
224
- transaction_operation_failed(FAIL_OTHER,
225
- fmt::format("error getting {} {}", id.key(), err_message.value_or(""))));
226
- }
227
- }
228
- } else {
229
- if (res) {
230
- auto err = forward_compat::check(forward_compat_stage::GETS, res->links().forward_compat());
231
- if (err) {
232
- return op_completed_with_error(std::move(cb), *err);
233
- }
234
- }
235
- return op_completed_with_callback(std::move(cb), res);
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 = hooks_.before_staged_replace(this, document.id().key());
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 = std::move(document), content, cb = std::move(cb), error_handler = std::move(error_handler)](
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
- auto err = hooks_.after_staged_replace_complete(this, document.id().key());
403
- if (err) {
404
- return error_handler(*err, "after_staged_replace_commit hook returned error", std::move(cb));
405
- }
406
- transaction_get_result out = document;
407
- out.cas(resp.cas.value());
408
- out.content(content);
409
- CB_ATTEMPT_CTX_LOG_TRACE(this, "replace staged content, result {}", out);
410
- staged_mutations_->add(staged_mutation(out, content, staged_mutation_type::REPLACE));
411
- return op_completed_with_callback(std::move(cb), std::optional<transaction_get_result>(out));
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
- if (auto ec = hooks_.before_check_atr_entry_for_blocking_doc(this, doc.id().key())) {
527
- return cb(transaction_operation_failed(FAIL_WRITE_WRITE_CONFLICT, "document is in another transaction").retry());
528
- }
529
- core::document_id atr_id(doc.links().atr_bucket_name().value(),
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
- // if we are here, there is still a write-write conflict
566
- return cb(transaction_operation_failed(FAIL_WRITE_WRITE_CONFLICT, "document is in another transaction").retry());
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
- if (auto ec = hooks_.before_staged_remove(this, document.id().key())) {
641
- return error_handler(*ec, "before_staged_remove hook raised error", std::move(cb));
642
- }
643
- CB_ATTEMPT_CTX_LOG_TRACE(this, "about to remove doc {} with cas {}", document.id(), document.cas().value());
644
- auto req = create_staging_request(document.id(), &document, "remove", op_id);
645
- req.cas = document.cas();
646
- req.access_deleted = document.links().is_deleted();
647
- overall_.cluster_ref().execute(
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
- return error_handler(*ec, resp.ctx.ec().message(), std::move(cb));
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
- if (auto err = hooks_.before_remove_staged_insert(this, id.key()); err) {
692
- return error_handler(*err, "before_remove_staged_insert hook returned error", std::move(cb));
693
- }
694
-
695
- core::operations::mutate_in_request req{ id };
696
- req.specs =
697
- couchbase::mutate_in_specs{
698
- couchbase::mutate_in_specs::remove("txn").xattr(),
699
- }
700
- .specs();
701
- wrap_durable_request(req, overall_.config());
702
- req.access_deleted = true;
703
-
704
- overall_.cluster_ref().execute(req,
705
- [this, id = std::move(id), cb = std::move(cb), error_handler = std::move(error_handler)](
706
- core::operations::mutate_in_response resp) mutable {
707
- auto ec = error_class_from_response(resp);
708
- if (!ec) {
709
-
710
- if (auto err = hooks_.after_remove_staged_insert(this, id.key()); err) {
711
- error_handler(*err, "after_remove_staged_insert hook returned error", std::move(cb));
712
- return;
713
- }
714
- staged_mutations_->remove_any(id);
715
- op_completed_with_callback(std::move(cb));
716
- return;
717
- }
718
- CB_ATTEMPT_CTX_LOG_DEBUG(this, "remove_staged_insert got error {}", *ec);
719
- return error_handler(*ec, resp.ctx.ec().message(), std::move(cb));
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
- if (auto ec = hooks_.before_query(this, statement)) {
1022
- auto err = std::make_exception_ptr(transaction_operation_failed(*ec, "before_query hook raised error"));
1023
- if (statement == BEGIN_WORK) {
1024
- return cb(std::make_exception_ptr(transaction_operation_failed(*ec, "before_query hook raised error").no_rollback()), {});
1025
- }
1026
- return cb(std::make_exception_ptr(transaction_operation_failed(*ec, "before_query hook raised error")), {});
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
- cb(handle_query_error(resp), resp);
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
- if (!!(ec = hooks_.before_atr_commit(this))) {
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
- if (!!(ec = hooks_.before_atr_commit_ambiguity_resolution(this))) {
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
- if (!!(ec = hooks_.before_atr_aborted(this))) {
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
- ec = hooks_.after_atr_aborted(this);
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
- if (!!(ec = hooks_.before_atr_rolled_back(this))) {
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
- if (auto ec = hooks_.before_atr_pending(this); ec) {
1932
- return error_handler(*ec, "before_atr_pending hook raised error", id, std::forward<Handler>(fn));
1933
- }
1934
- CB_ATTEMPT_CTX_LOG_DEBUG(this, "updating atr {}", atr_id_.value());
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
- req.specs =
1946
- couchbase::mutate_in_specs{
1947
- couchbase::mutate_in_specs::insert(prefix + ATR_FIELD_TRANSACTION_ID, overall_.transaction_id()).xattr().create_path(),
1948
- couchbase::mutate_in_specs::insert(prefix + ATR_FIELD_STATUS, attempt_state_name(attempt_state::PENDING))
1949
- .xattr()
1950
- .create_path(),
1951
- couchbase::mutate_in_specs::insert(prefix + ATR_FIELD_START_TIMESTAMP, subdoc::mutate_in_macro::cas)
1952
- .xattr()
1953
- .create_path(),
1954
- couchbase::mutate_in_specs::insert(prefix + ATR_FIELD_EXPIRES_AFTER_MSECS, remaining_bounded_msecs).xattr().create_path(),
1955
- // ExtStoreDurability
1956
- couchbase::mutate_in_specs::insert(prefix + ATR_FIELD_DURABILITY_LEVEL,
1957
- store_durability_level_to_string(overall_.config().level))
1958
- .xattr()
1959
- .create_path(),
1960
- // subdoc::opcode::set_doc used in replace w/ empty path
1961
- // ExtBinaryMetadata
1962
- couchbase::mutate_in_specs::replace_raw({}, std::vector<std::byte>{ std::byte{ 0x00 } }),
1963
- }
1964
- .specs();
1965
- req.store_semantics = couchbase::store_semantics::upsert;
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
- wrap_durable_request(req, overall_.config());
1968
- overall_.cluster_ref().execute(
1969
- req, [this, fn = std::forward<Handler>(fn), error_handler](core::operations::mutate_in_response resp) mutable {
1970
- auto ec = error_class_from_response(resp);
1971
- if (!ec) {
1972
- ec = hooks_.after_atr_pending(this);
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
- if (auto ec = hooks_.before_doc_get(this, id.key()); ec) {
2038
- return cb(ec, "before_doc_get hook raised error", std::nullopt);
2039
- }
2040
-
2041
- get_doc(
2042
- id,
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
- return cb(std::nullopt, std::nullopt, doc);
2064
- }
2065
-
2066
- core::document_id doc_atr_id{ doc->links().atr_bucket_name().value(),
2067
- doc->links().atr_scope_name().value(),
2068
- doc->links().atr_collection_name().value(),
2069
- doc->links().atr_id().value() };
2070
- active_transaction_record::get_atr(
2071
- cluster_ref(),
2072
- doc_atr_id,
2073
- [this, id, doc, cb = std::move(cb)](std::error_code ec2, std::optional<active_transaction_record> atr) mutable {
2074
- if (!ec2 && atr) {
2075
- active_transaction_record& atr_doc = atr.value();
2076
- std::optional<atr_entry> entry;
2077
- for (const auto& e : atr_doc.entries()) {
2078
- if (doc->links().staged_attempt_id().value() == e.attempt_id()) {
2079
- entry.emplace(e);
2080
- break;
2081
- }
2082
- }
2083
- bool ignore_doc = false;
2084
- auto content = doc->content();
2085
- if (entry) {
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
- } else {
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
- if (auto err = hooks_.before_get_doc_in_exists_during_staged_insert(this, id.key()); err) {
2254
- return error_handler(
2255
- *err, fmt::format("before_get_doc_in_exists_during_staged_insert hook raised {}", *err), std::forward<Handler>(cb));
2256
- }
2257
- return get_doc(
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::move(cb), transaction_operation_failed(ec, "failed in create_staged_insert").retry());
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
- if (auto ec = hooks_.before_staged_insert(this, id.key()); ec) {
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 = resp.ctx.ec() ? error_class_from_response(resp) : hooks_.after_staged_insert_complete(this, id.key());
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, msg);
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 create_staged_insert_error_handler(id,
2404
- content,
2405
- cas,
2406
- std::forward<Delay>(delay),
2407
- op_id,
2408
- std::forward<Handler>(cb),
2409
- error_class_from_response(resp).value(),
2410
- resp.ctx.ec().message());
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