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.
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