couchbase 4.4.6 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/deps/couchbase-cxx-cache/mozilla-ca-bundle.crt +79 -165
  2. package/deps/couchbase-cxx-cache/mozilla-ca-bundle.sha256 +1 -1
  3. package/deps/couchbase-cxx-client/CMakeLists.txt +14 -3
  4. package/deps/couchbase-cxx-client/README.md +2 -2
  5. package/deps/couchbase-cxx-client/cmake/Profiler.cmake +15 -0
  6. package/deps/couchbase-cxx-client/core/app_telemetry_address.cxx +55 -0
  7. package/deps/couchbase-cxx-client/core/app_telemetry_address.hxx +39 -0
  8. package/deps/couchbase-cxx-client/core/app_telemetry_meter.cxx +791 -0
  9. package/deps/couchbase-cxx-client/core/app_telemetry_meter.hxx +200 -0
  10. package/deps/couchbase-cxx-client/core/app_telemetry_reporter.cxx +895 -0
  11. package/deps/couchbase-cxx-client/core/app_telemetry_reporter.hxx +59 -0
  12. package/deps/couchbase-cxx-client/core/bucket.cxx +77 -35
  13. package/deps/couchbase-cxx-client/core/bucket.hxx +17 -10
  14. package/deps/couchbase-cxx-client/core/cluster.cxx +56 -17
  15. package/deps/couchbase-cxx-client/core/cluster_credentials.cxx +27 -0
  16. package/deps/couchbase-cxx-client/core/cluster_credentials.hxx +36 -0
  17. package/deps/couchbase-cxx-client/core/cluster_options.hxx +13 -0
  18. package/deps/couchbase-cxx-client/core/collections_component.cxx +7 -5
  19. package/deps/couchbase-cxx-client/core/http_component.cxx +6 -0
  20. package/deps/couchbase-cxx-client/core/impl/bucket_manager.cxx +2 -0
  21. package/deps/couchbase-cxx-client/core/impl/cluster.cxx +10 -0
  22. package/deps/couchbase-cxx-client/core/impl/collection.cxx +2 -0
  23. package/deps/couchbase-cxx-client/core/impl/error.cxx +22 -6
  24. package/deps/couchbase-cxx-client/core/impl/error.hxx +1 -1
  25. package/deps/couchbase-cxx-client/core/impl/logger.cxx +51 -0
  26. package/deps/couchbase-cxx-client/core/impl/replica_utils.cxx +1 -1
  27. package/deps/couchbase-cxx-client/core/impl/transaction_get_multi_replicas_from_preferred_server_group_spec.cxx +32 -0
  28. package/deps/couchbase-cxx-client/core/impl/transaction_get_multi_spec.cxx +30 -0
  29. package/deps/couchbase-cxx-client/core/impl/transaction_op_error_category.cxx +2 -0
  30. package/deps/couchbase-cxx-client/core/io/config_tracker.cxx +6 -6
  31. package/deps/couchbase-cxx-client/core/io/http_command.hxx +35 -11
  32. package/deps/couchbase-cxx-client/core/io/http_session.cxx +10 -0
  33. package/deps/couchbase-cxx-client/core/io/http_session.hxx +4 -0
  34. package/deps/couchbase-cxx-client/core/io/http_session_manager.hxx +87 -35
  35. package/deps/couchbase-cxx-client/core/io/mcbp_command.hxx +41 -2
  36. package/deps/couchbase-cxx-client/core/io/mcbp_session.cxx +52 -19
  37. package/deps/couchbase-cxx-client/core/io/mcbp_session.hxx +3 -0
  38. package/deps/couchbase-cxx-client/core/logger/logger.cxx +46 -0
  39. package/deps/couchbase-cxx-client/core/logger/logger.hxx +41 -1
  40. package/deps/couchbase-cxx-client/core/management/bucket_settings.hxx +1 -0
  41. package/deps/couchbase-cxx-client/core/management/bucket_settings_json.hxx +4 -0
  42. package/deps/couchbase-cxx-client/core/meta/features.hxx +32 -0
  43. package/deps/couchbase-cxx-client/core/operations/document_analytics.cxx +9 -9
  44. package/deps/couchbase-cxx-client/core/operations/document_get_all_replicas.hxx +10 -2
  45. package/deps/couchbase-cxx-client/core/operations/document_lookup_in.cxx +4 -0
  46. package/deps/couchbase-cxx-client/core/operations/document_lookup_in_all_replicas.hxx +14 -2
  47. package/deps/couchbase-cxx-client/core/operations/document_lookup_in_any_replica.hxx +4 -0
  48. package/deps/couchbase-cxx-client/core/operations/document_mutate_in.cxx +4 -0
  49. package/deps/couchbase-cxx-client/core/operations/document_mutate_in.hxx +1 -0
  50. package/deps/couchbase-cxx-client/core/operations/document_query.cxx +12 -10
  51. package/deps/couchbase-cxx-client/core/operations/http_noop.cxx +1 -0
  52. package/deps/couchbase-cxx-client/core/operations/management/bucket_create.cxx +3 -0
  53. package/deps/couchbase-cxx-client/core/operations/management/bucket_update.cxx +3 -0
  54. package/deps/couchbase-cxx-client/core/operations/management/search_index_get_all.cxx +3 -2
  55. package/deps/couchbase-cxx-client/core/origin.cxx +25 -5
  56. package/deps/couchbase-cxx-client/core/origin.hxx +18 -24
  57. package/deps/couchbase-cxx-client/core/platform/random.cc +6 -3
  58. package/deps/couchbase-cxx-client/core/platform/random.h +2 -2
  59. package/deps/couchbase-cxx-client/core/protocol/cmd_mutate_in.hxx +9 -0
  60. package/deps/couchbase-cxx-client/core/timeout_defaults.hxx +4 -0
  61. package/deps/couchbase-cxx-client/core/topology/configuration.cxx +10 -13
  62. package/deps/couchbase-cxx-client/core/topology/configuration.hxx +14 -15
  63. package/deps/couchbase-cxx-client/core/topology/configuration_json.hxx +6 -0
  64. package/deps/couchbase-cxx-client/core/transactions/async_attempt_context.hxx +22 -2
  65. package/deps/couchbase-cxx-client/core/transactions/attempt_context.hxx +25 -7
  66. package/deps/couchbase-cxx-client/core/transactions/attempt_context_impl.cxx +723 -245
  67. package/deps/couchbase-cxx-client/core/transactions/attempt_context_impl.hxx +91 -12
  68. package/deps/couchbase-cxx-client/core/transactions/exceptions.cxx +5 -0
  69. package/deps/couchbase-cxx-client/core/transactions/exceptions.hxx +20 -0
  70. package/deps/couchbase-cxx-client/core/transactions/exceptions_fmt.hxx +3 -0
  71. package/deps/couchbase-cxx-client/core/transactions/forward_compat.cxx +71 -6
  72. package/deps/couchbase-cxx-client/core/transactions/forward_compat.hxx +45 -59
  73. package/deps/couchbase-cxx-client/core/transactions/get_multi_orchestrator.cxx +616 -0
  74. package/deps/couchbase-cxx-client/core/transactions/get_multi_orchestrator.hxx +61 -0
  75. package/deps/couchbase-cxx-client/core/transactions/internal/doc_record.cxx +8 -0
  76. package/deps/couchbase-cxx-client/core/transactions/internal/doc_record.hxx +16 -5
  77. package/deps/couchbase-cxx-client/core/transactions/internal/exceptions_internal.hxx +12 -0
  78. package/deps/couchbase-cxx-client/core/transactions/internal/transaction_context.hxx +13 -0
  79. package/deps/couchbase-cxx-client/core/transactions/internal/transaction_fields.hxx +1 -0
  80. package/deps/couchbase-cxx-client/core/transactions/staged_mutation.cxx +277 -96
  81. package/deps/couchbase-cxx-client/core/transactions/staged_mutation.hxx +28 -76
  82. package/deps/couchbase-cxx-client/core/transactions/transaction_context.cxx +33 -0
  83. package/deps/couchbase-cxx-client/core/transactions/transaction_get_multi_mode.hxx +28 -0
  84. package/deps/couchbase-cxx-client/core/transactions/transaction_get_multi_replicas_from_preferred_server_group_mode.hxx +27 -0
  85. package/deps/couchbase-cxx-client/core/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +72 -0
  86. package/deps/couchbase-cxx-client/core/transactions/transaction_get_multi_result.hxx +67 -0
  87. package/deps/couchbase-cxx-client/core/transactions/transaction_links.hxx +10 -0
  88. package/deps/couchbase-cxx-client/core/transactions/transactions.cxx +8 -3
  89. package/deps/couchbase-cxx-client/core/utils/connection_string.cxx +7 -0
  90. package/deps/couchbase-cxx-client/core/utils/connection_string.hxx +1 -0
  91. package/deps/couchbase-cxx-client/core/utils/url_codec.cxx +26 -0
  92. package/deps/couchbase-cxx-client/core/utils/url_codec.hxx +11 -0
  93. package/deps/couchbase-cxx-client/core/websocket_codec.cxx +647 -0
  94. package/deps/couchbase-cxx-client/core/websocket_codec.hxx +77 -0
  95. package/deps/couchbase-cxx-client/couchbase/analytics_options.hxx +70 -6
  96. package/deps/couchbase-cxx-client/couchbase/application_telemetry_options.hxx +124 -0
  97. package/deps/couchbase-cxx-client/couchbase/behavior_options.hxx +9 -0
  98. package/deps/couchbase-cxx-client/couchbase/cluster_options.hxx +17 -0
  99. package/deps/couchbase-cxx-client/couchbase/error_codes.hxx +1 -0
  100. package/deps/couchbase-cxx-client/couchbase/logger.hxx +16 -0
  101. package/deps/couchbase-cxx-client/couchbase/management/bucket_settings.hxx +1 -0
  102. package/deps/couchbase-cxx-client/couchbase/query_options.hxx +70 -6
  103. package/deps/couchbase-cxx-client/couchbase/transactions/async_attempt_context.hxx +29 -5
  104. package/deps/couchbase-cxx-client/couchbase/transactions/attempt_context.hxx +24 -7
  105. package/deps/couchbase-cxx-client/couchbase/transactions/transaction_get_multi_mode.hxx +47 -0
  106. package/deps/couchbase-cxx-client/couchbase/transactions/transaction_get_multi_options.hxx +44 -0
  107. package/deps/couchbase-cxx-client/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_mode.hxx +46 -0
  108. package/deps/couchbase-cxx-client/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_options.hxx +48 -0
  109. package/deps/couchbase-cxx-client/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +112 -0
  110. package/deps/couchbase-cxx-client/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_spec.hxx +47 -0
  111. package/deps/couchbase-cxx-client/couchbase/transactions/transaction_get_multi_result.hxx +105 -0
  112. package/deps/couchbase-cxx-client/couchbase/transactions/transaction_get_multi_spec.hxx +45 -0
  113. package/dist/analyticsindexmanager.d.ts +1 -0
  114. package/dist/binarycollection.d.ts +1 -0
  115. package/dist/binding.d.ts +82 -38
  116. package/dist/binding.js +14 -7
  117. package/dist/bindingutilities.d.ts +16 -4
  118. package/dist/bindingutilities.js +233 -31
  119. package/dist/cluster.d.ts +39 -0
  120. package/dist/cluster.js +21 -3
  121. package/dist/collection.d.ts +20 -1
  122. package/dist/collection.js +13 -0
  123. package/dist/errorcontexts.d.ts +24 -0
  124. package/dist/errorcontexts.js +12 -6
  125. package/dist/generaltypes.d.ts +16 -0
  126. package/dist/generaltypes.js +18 -1
  127. package/dist/httpexecutor.d.ts +1 -2
  128. package/dist/httpexecutor.js +0 -9
  129. package/dist/streamablepromises.d.ts +25 -7
  130. package/dist/streamablepromises.js +32 -7
  131. package/dist/transactions.d.ts +239 -1
  132. package/dist/transactions.js +316 -2
  133. package/dist/transcoders.d.ts +1 -0
  134. package/dist/utilities.d.ts +1 -0
  135. package/package.json +24 -24
  136. package/src/connection.cpp +34 -4
  137. package/src/constants.cpp +124 -0
  138. package/src/jstocbpp_autogen.hpp +22 -8
  139. package/src/jstocbpp_transactions.hpp +76 -2
  140. package/src/transaction.cpp +101 -0
  141. package/src/transaction.hpp +5 -0
  142. package/tools/gen-bindings-js.js +2 -1
  143. package/tools/gen-bindings-json.py +28 -3
@@ -21,12 +21,18 @@
21
21
  #include "attempt_context_testing_hooks.hxx"
22
22
  #include "attempt_state.hxx"
23
23
  #include "core/cluster.hxx"
24
+ #include "core/error_context/transaction_error_context.hxx"
25
+ #include "core/error_context/transaction_op_error_context.hxx"
24
26
  #include "core/impl/error.hxx"
25
27
  #include "core/operations.hxx"
26
28
  #include "core/transactions/error_class.hxx"
29
+ #include "core/transactions/transaction_get_multi_mode.hxx"
30
+ #include "couchbase/error_codes.hxx"
27
31
  #include "durability_level.hxx"
28
32
  #include "exceptions.hxx"
33
+ #include "exceptions_fmt.hxx"
29
34
  #include "forward_compat.hxx"
35
+ #include "get_multi_orchestrator.hxx"
30
36
  #include "internal/exceptions_internal.hxx"
31
37
  #include "internal/exceptions_internal_fmt.hxx"
32
38
  #include "internal/logging.hxx"
@@ -36,6 +42,8 @@
36
42
  #include "internal/utils.hxx"
37
43
  #include "staged_mutation.hxx"
38
44
 
45
+ #include <optional>
46
+
39
47
  namespace couchbase::core::transactions
40
48
  {
41
49
 
@@ -95,7 +103,7 @@ wrap_call_for_public_api(std::function<transaction_get_result()>&& handler)
95
103
  } catch (const transaction_operation_failed& e) {
96
104
  return { core::impl::make_error(e), {} };
97
105
  } catch (const op_exception& ex) {
98
- return { core::impl::make_error(ex.ctx()), {} };
106
+ return { core::impl::make_error(ex), {} };
99
107
  } catch (...) {
100
108
  // the handler should catch everything else, but just in case...
101
109
  return { { errc::transaction_op::generic }, {} };
@@ -115,7 +123,7 @@ wrap_callback_for_async_public_api(
115
123
  try {
116
124
  std::rethrow_exception(err);
117
125
  } catch (const op_exception& e) {
118
- return cb(core::impl::make_error(e.ctx()), {});
126
+ return cb(core::impl::make_error(e), {});
119
127
  } catch (const transaction_operation_failed& e) {
120
128
  return cb(core::impl::make_error(e), {});
121
129
  } catch (...) {
@@ -235,9 +243,10 @@ attempt_context_impl::get(const core::document_id& id, Callback&& cb)
235
243
  false,
236
244
  std::nullopt,
237
245
  [self, id, cb = std::move(cb)](std::optional<error_class> ec,
246
+ std::optional<external_exception> cause,
238
247
  const std::optional<std::string>& err_message,
239
248
  std::optional<transaction_get_result> res) mutable {
240
- auto handler = [self, id, err_message, res = std::move(res), cb = std::move(cb)](
249
+ auto handler = [self, id, cause, err_message, res = std::move(res), cb = std::move(cb)](
241
250
  std::optional<error_class> ec) mutable {
242
251
  if (ec) {
243
252
  switch (*ec) {
@@ -268,8 +277,8 @@ attempt_context_impl::get(const core::document_id& id, Callback&& cb)
268
277
  err_message.value_or(""),
269
278
  *ec,
270
279
  id.key());
271
- return self->op_completed_with_error(std::move(cb),
272
- transaction_operation_failed(FAIL_OTHER, msg));
280
+ return self->op_completed_with_error(
281
+ std::move(cb), transaction_operation_failed(FAIL_OTHER, cause, msg));
273
282
  }
274
283
  }
275
284
  } else {
@@ -330,9 +339,10 @@ attempt_context_impl::get_optional(const core::document_id& id, Callback&& cb)
330
339
  false,
331
340
  std::nullopt,
332
341
  [self, id, cb = std::move(cb)](std::optional<error_class> ec,
342
+ std::optional<external_exception> cause,
333
343
  const std::optional<std::string>& err_message,
334
344
  std::optional<transaction_get_result> res) mutable {
335
- auto handler = [self, id, err_message, res, cb = std::move(cb)](
345
+ auto handler = [self, id, cause, err_message, res, cb = std::move(cb)](
336
346
  std::optional<error_class> ec) mutable {
337
347
  if (ec) {
338
348
  switch (*ec) {
@@ -341,6 +351,7 @@ attempt_context_impl::get_optional(const core::document_id& id, Callback&& cb)
341
351
  std::move(cb),
342
352
  transaction_operation_failed(
343
353
  *ec,
354
+ cause,
344
355
  fmt::format("transaction expired during get {}", err_message.value_or("")))
345
356
  .expired());
346
357
  case FAIL_DOC_NOT_FOUND:
@@ -350,19 +361,22 @@ attempt_context_impl::get_optional(const core::document_id& id, Callback&& cb)
350
361
  return self->op_completed_with_error(
351
362
  std::move(cb),
352
363
  transaction_operation_failed(
353
- *ec, fmt::format("transient failure in get {}", err_message.value_or("")))
364
+ *ec,
365
+ cause,
366
+ fmt::format("transient failure in get {}", err_message.value_or("")))
354
367
  .retry());
355
368
  case FAIL_HARD:
356
369
  return self->op_completed_with_error(
357
370
  std::move(cb),
358
371
  transaction_operation_failed(
359
- *ec, fmt::format("fail hard in get {}", err_message.value_or("")))
372
+ *ec, cause, fmt::format("fail hard in get {}", err_message.value_or("")))
360
373
  .no_rollback());
361
374
  default: {
362
375
  return self->op_completed_with_error(
363
376
  std::move(cb),
364
377
  transaction_operation_failed(
365
378
  FAIL_OTHER,
379
+ cause,
366
380
  fmt::format("error getting {} {}", id.key(), err_message.value_or(""))));
367
381
  }
368
382
  }
@@ -393,10 +407,10 @@ attempt_context_impl::get_replica_from_preferred_server_group(
393
407
  std::function<void(std::exception_ptr, std::optional<transaction_get_result>)>&& cb)
394
408
  {
395
409
  if (op_list_.get_mode().is_query()) {
396
- return cb(std::make_exception_ptr(transaction_operation_failed(
397
- FAIL_OTHER, "Replica Read is not supported in Query Mode")
398
- .cause(FEATURE_NOT_AVAILABLE_EXCEPTION)),
399
- {});
410
+ return cb(
411
+ std::make_exception_ptr(transaction_operation_failed(
412
+ FAIL_OTHER, FEATURE_NOT_AVAILABLE_EXCEPTION, "Get Replica is not supported in Query Mode")),
413
+ {});
400
414
  }
401
415
  cache_error_async(cb, [self = shared_from_this(), id, cb]() mutable {
402
416
  self->check_if_done(cb);
@@ -405,16 +419,18 @@ attempt_context_impl::get_replica_from_preferred_server_group(
405
419
  true,
406
420
  std::nullopt,
407
421
  [self, id, cb = std::move(cb)](std::optional<error_class> ec,
422
+ std::optional<external_exception> cause,
408
423
  const std::optional<std::string>& err_message,
409
424
  std::optional<transaction_get_result> res) mutable {
410
- auto handler = [self, id, err_message, res = std::move(res), cb = std::move(cb)](
425
+ auto handler = [self, id, cause, err_message, res = std::move(res), cb = std::move(cb)](
411
426
  std::optional<error_class> ec) mutable {
412
427
  if (ec) {
413
428
  switch (*ec) {
414
429
  case FAIL_EXPIRY:
415
430
  return self->op_completed_with_error(
416
431
  std::move(cb),
417
- transaction_operation_failed(*ec, "transaction expired during get").expired());
432
+ transaction_operation_failed(*ec, cause, "transaction expired during get")
433
+ .expired());
418
434
  case FAIL_DOC_NOT_FOUND:
419
435
  return self->op_completed_with_callback(std::move(cb),
420
436
  std::optional<transaction_get_result>());
@@ -422,18 +438,22 @@ attempt_context_impl::get_replica_from_preferred_server_group(
422
438
  return self->op_completed_with_error(
423
439
  std::move(cb),
424
440
  transaction_operation_failed(
425
- *ec, fmt::format("transient failure in get {}", err_message.value_or("")))
441
+ *ec,
442
+ cause,
443
+ fmt::format("transient failure in get {}", err_message.value_or("")))
426
444
  .retry());
427
445
  case FAIL_HARD:
428
446
  return self->op_completed_with_error(
429
447
  std::move(cb),
430
448
  transaction_operation_failed(
431
- *ec, fmt::format("fail hard in get {}", err_message.value_or("")))
449
+ *ec, cause, fmt::format("fail hard in get {}", err_message.value_or("")))
432
450
  .no_rollback());
433
451
  case FAIL_OTHER:
434
- if (err_message.value_or("") == "document_irretrievable (102)") {
435
- return self->op_completed_with_callback(std::move(cb),
436
- std::optional<transaction_get_result>());
452
+ if (cause == DOCUMENT_UNRETRIEVABLE_EXCEPTION) {
453
+ return self->op_completed_with_callback(
454
+ std::move(cb),
455
+ transaction_operation_failed(FAIL_OTHER, cause, "failed to retrieve document"),
456
+ std::move(res));
437
457
  }
438
458
  [[fallthrough]];
439
459
  default: {
@@ -441,14 +461,16 @@ attempt_context_impl::get_replica_from_preferred_server_group(
441
461
  err_message.value_or(""),
442
462
  *ec,
443
463
  id.key());
444
- return self->op_completed_with_error(std::move(cb),
445
- transaction_operation_failed(FAIL_OTHER, msg));
464
+ return self->op_completed_with_error(
465
+ std::move(cb), transaction_operation_failed(FAIL_OTHER, cause, msg));
446
466
  }
447
467
  }
448
468
  } else {
449
469
  if (!res) {
450
470
  return self->op_completed_with_error(
451
- std::move(cb), transaction_operation_failed(*ec, "document not found"));
471
+ std::move(cb),
472
+ transaction_operation_failed(
473
+ *ec, external_exception::DOCUMENT_NOT_FOUND_EXCEPTION, "document not found"));
452
474
  }
453
475
  auto err =
454
476
  check_forward_compat(forward_compat_stage::GETS, res->links().forward_compat());
@@ -476,7 +498,7 @@ attempt_context_impl::get_replica_from_preferred_server_group(const core::docume
476
498
  get_replica_from_preferred_server_group(
477
499
  id, [barrier](const std::exception_ptr& err, std::optional<transaction_get_result> res) {
478
500
  if (err) {
479
- barrier->set_exception(err);
501
+ return barrier->set_exception(err);
480
502
  }
481
503
  return barrier->set_value(std::move(res));
482
504
  });
@@ -520,6 +542,277 @@ attempt_context_impl::get_replica_from_preferred_server_group(
520
542
  });
521
543
  }
522
544
 
545
+ namespace
546
+ {
547
+ // FIXME(SA): do not use Public API in the core
548
+ auto
549
+ from_public_api(couchbase::transactions::transaction_get_multi_mode mode)
550
+ -> transaction_get_multi_mode
551
+ {
552
+ switch (mode) {
553
+ case couchbase::transactions::transaction_get_multi_mode::prioritise_latency:
554
+ return transaction_get_multi_mode::prioritise_latency;
555
+ case couchbase::transactions::transaction_get_multi_mode::disable_read_skew_detection:
556
+ return transaction_get_multi_mode::disable_read_skew_detection;
557
+ case couchbase::transactions::transaction_get_multi_mode::prioritise_read_skew_detection:
558
+ return transaction_get_multi_mode::prioritise_read_skew_detection;
559
+ }
560
+ return transaction_get_multi_mode::prioritise_latency;
561
+ }
562
+
563
+ auto
564
+ from_public_api(
565
+ couchbase::transactions::transaction_get_multi_replicas_from_preferred_server_group_mode mode)
566
+ -> transaction_get_multi_replicas_from_preferred_server_group_mode
567
+ {
568
+ switch (mode) {
569
+ case couchbase::transactions::transaction_get_multi_replicas_from_preferred_server_group_mode::
570
+ prioritise_latency:
571
+ return transaction_get_multi_replicas_from_preferred_server_group_mode::prioritise_latency;
572
+ case couchbase::transactions::transaction_get_multi_replicas_from_preferred_server_group_mode::
573
+ disable_read_skew_detection:
574
+ return transaction_get_multi_replicas_from_preferred_server_group_mode::
575
+ disable_read_skew_detection;
576
+ case couchbase::transactions::transaction_get_multi_replicas_from_preferred_server_group_mode::
577
+ prioritise_read_skew_detection:
578
+ return transaction_get_multi_replicas_from_preferred_server_group_mode::
579
+ prioritise_read_skew_detection;
580
+ break;
581
+ }
582
+ return transaction_get_multi_replicas_from_preferred_server_group_mode::prioritise_latency;
583
+ }
584
+ } // namespace
585
+
586
+ void
587
+ attempt_context_impl::get_multi(
588
+ const std::vector<core::document_id>& ids,
589
+ transaction_get_multi_mode mode,
590
+ std::function<void(std::exception_ptr, std::optional<transaction_get_multi_result>)>&& cb)
591
+ {
592
+ if (op_list_.get_mode().is_query()) {
593
+ return cb(std::make_exception_ptr(op_exception({ errc::transaction_op::feature_not_available },
594
+ "Get Multi is not supported in Query Mode",
595
+ FEATURE_NOT_AVAILABLE_EXCEPTION)),
596
+ {});
597
+ }
598
+ cache_error_async(cb, [self = shared_from_this(), ids, mode, cb]() mutable {
599
+ self->check_if_done(cb);
600
+ const auto manager = std::make_shared<get_multi_orchestrator>(self, ids);
601
+ manager->get_multi(
602
+ mode,
603
+ [self, cb = std::move(cb)](std::exception_ptr err,
604
+ std::optional<transaction_get_multi_result> res) mutable {
605
+ if (err) {
606
+ self->op_completed_with_error(std::move(cb), std::move(err));
607
+ return;
608
+ }
609
+ self->op_completed_with_callback(std::move(cb), std::move(res));
610
+ });
611
+ });
612
+ }
613
+
614
+ auto
615
+ attempt_context_impl::get_multi(const std::vector<core::document_id>& ids,
616
+ transaction_get_multi_mode mode) -> transaction_get_multi_result
617
+ {
618
+ auto barrier = std::make_shared<std::promise<transaction_get_multi_result>>();
619
+ auto f = barrier->get_future();
620
+ get_multi(
621
+ ids, mode, [barrier](std::exception_ptr err, std::optional<transaction_get_multi_result> res) {
622
+ if (err) {
623
+ return barrier->set_exception(std::move(err));
624
+ }
625
+ if (res) {
626
+ return barrier->set_value(std::move(*res));
627
+ }
628
+ return barrier->set_exception(std::make_exception_ptr(
629
+ transaction_operation_failed(FAIL_OTHER, "get_multi: either error or result must be set")));
630
+ });
631
+ return f.get();
632
+ }
633
+
634
+ void
635
+ attempt_context_impl::get_multi(
636
+ const std::vector<couchbase::transactions::transaction_get_multi_spec>& specs,
637
+ const couchbase::transactions::transaction_get_multi_options& options,
638
+ std::function<void(error, std::optional<couchbase::transactions::transaction_get_multi_result>)>&&
639
+ cb)
640
+ {
641
+ std::vector<core::document_id> ids;
642
+ ids.reserve(specs.size());
643
+ for (const auto& spec : specs) {
644
+ ids.emplace_back(spec.bucket_, spec.scope_, spec.collection_, spec.id_);
645
+ }
646
+
647
+ get_multi(ids, from_public_api(options.mode_), [cb = std::move(cb)](const auto& err, auto res) {
648
+ if (err) {
649
+ try {
650
+ std::rethrow_exception(err);
651
+ } catch (const op_exception& e) {
652
+ return cb(core::impl::make_error(e), {});
653
+ } catch (const transaction_operation_failed& e) {
654
+ return cb(core::impl::make_error(e), {});
655
+ } catch (...) {
656
+ return cb({ errc::transaction_op::generic }, {});
657
+ }
658
+ }
659
+ if (res) {
660
+ return cb({}, couchbase::transactions::transaction_get_multi_result{ res->content() });
661
+ }
662
+ return cb({ errc::transaction_op::generic }, {});
663
+ });
664
+ }
665
+
666
+ auto
667
+ attempt_context_impl::get_multi(
668
+ const std::vector<couchbase::transactions::transaction_get_multi_spec>& specs,
669
+ const couchbase::transactions::transaction_get_multi_options& options)
670
+ -> std::pair<error, std::optional<couchbase::transactions::transaction_get_multi_result>>
671
+ {
672
+ auto barrier = std::make_shared<std::promise<
673
+ std::pair<error, std::optional<couchbase::transactions::transaction_get_multi_result>>>>();
674
+ auto f = barrier->get_future();
675
+ get_multi(
676
+ specs,
677
+ options,
678
+ [barrier](error err, std::optional<couchbase::transactions::transaction_get_multi_result> res) {
679
+ return barrier->set_value(std::make_pair(std::move(err), std::move(res)));
680
+ });
681
+ return f.get();
682
+ }
683
+
684
+ void
685
+ attempt_context_impl::get_multi_replicas_from_preferred_server_group(
686
+ const std::vector<core::document_id>& ids,
687
+ transaction_get_multi_replicas_from_preferred_server_group_mode mode,
688
+ std::function<
689
+ void(std::exception_ptr,
690
+ std::optional<transaction_get_multi_replicas_from_preferred_server_group_result>)>&& cb)
691
+ {
692
+ if (op_list_.get_mode().is_query()) {
693
+ return cb(
694
+ std::make_exception_ptr(op_exception({ errc::transaction_op::feature_not_available },
695
+ "Get Multi Replica is not supported in Query Mode",
696
+ FEATURE_NOT_AVAILABLE_EXCEPTION)),
697
+ {});
698
+ }
699
+
700
+ cache_error_async(cb, [self = shared_from_this(), ids, mode, cb]() mutable {
701
+ self->check_if_done(cb);
702
+ const auto manager = std::make_shared<get_multi_orchestrator>(self, ids);
703
+ manager->get_multi_replicas_from_preferred_server_group(
704
+ mode,
705
+ [self, cb = std::move(cb)](
706
+ std::exception_ptr err,
707
+ std::optional<transaction_get_multi_replicas_from_preferred_server_group_result>
708
+ res) mutable {
709
+ if (err) {
710
+ self->op_completed_with_error(std::move(cb), std::move(err));
711
+ return;
712
+ }
713
+ self->op_completed_with_callback(std::move(cb), std::move(res));
714
+ });
715
+ });
716
+ }
717
+
718
+ auto
719
+ attempt_context_impl::get_multi_replicas_from_preferred_server_group(
720
+ const std::vector<core::document_id>& ids,
721
+ transaction_get_multi_replicas_from_preferred_server_group_mode mode)
722
+ -> transaction_get_multi_replicas_from_preferred_server_group_result
723
+ {
724
+ auto barrier = std::make_shared<
725
+ std::promise<transaction_get_multi_replicas_from_preferred_server_group_result>>();
726
+ auto f = barrier->get_future();
727
+ get_multi_replicas_from_preferred_server_group(
728
+ ids,
729
+ mode,
730
+ [barrier](
731
+ std::exception_ptr err,
732
+ std::optional<transaction_get_multi_replicas_from_preferred_server_group_result> res) {
733
+ if (err) {
734
+ return barrier->set_exception(std::move(err));
735
+ }
736
+ if (res) {
737
+ return barrier->set_value(std::move(*res));
738
+ }
739
+ return barrier->set_exception(std::make_exception_ptr(
740
+ transaction_operation_failed(FAIL_OTHER, "get_multi: either error or result must be set")));
741
+ });
742
+ return f.get();
743
+ }
744
+
745
+ void
746
+ attempt_context_impl::get_multi_replicas_from_preferred_server_group(
747
+ const std::vector<
748
+ couchbase::transactions::transaction_get_multi_replicas_from_preferred_server_group_spec>&
749
+ specs,
750
+ const couchbase::transactions::transaction_get_multi_replicas_from_preferred_server_group_options&
751
+ options,
752
+ std::function<
753
+ void(error,
754
+ std::optional<couchbase::transactions::
755
+ transaction_get_multi_replicas_from_preferred_server_group_result>)>&& cb)
756
+ {
757
+ std::vector<core::document_id> ids;
758
+ ids.reserve(specs.size());
759
+ for (const auto& spec : specs) {
760
+ ids.emplace_back(spec.bucket_, spec.scope_, spec.collection_, spec.id_);
761
+ }
762
+
763
+ get_multi_replicas_from_preferred_server_group(
764
+ ids, from_public_api(options.mode_), [cb = std::move(cb)](const auto& err, auto res) {
765
+ if (err) {
766
+ try {
767
+ std::rethrow_exception(err);
768
+ } catch (const op_exception& e) {
769
+ return cb(core::impl::make_error(e), {});
770
+ } catch (const transaction_operation_failed& e) {
771
+ return cb(core::impl::make_error(e), {});
772
+ } catch (...) {
773
+ return cb({ errc::transaction_op::generic }, {});
774
+ }
775
+ }
776
+ if (res) {
777
+ return cb(
778
+ {},
779
+ couchbase::transactions::
780
+ transaction_get_multi_replicas_from_preferred_server_group_result{ res->content() });
781
+ }
782
+ return cb({ errc::transaction_op::generic }, {});
783
+ });
784
+ }
785
+
786
+ auto
787
+ attempt_context_impl::get_multi_replicas_from_preferred_server_group(
788
+ const std::vector<
789
+ couchbase::transactions::transaction_get_multi_replicas_from_preferred_server_group_spec>&
790
+ specs,
791
+ const couchbase::transactions::transaction_get_multi_replicas_from_preferred_server_group_options&
792
+ options)
793
+ -> std::pair<
794
+ error,
795
+ std::optional<
796
+ couchbase::transactions::transaction_get_multi_replicas_from_preferred_server_group_result>>
797
+ {
798
+ auto barrier = std::make_shared<std::promise<std::pair<
799
+ error,
800
+ std::optional<couchbase::transactions::
801
+ transaction_get_multi_replicas_from_preferred_server_group_result>>>>();
802
+ auto f = barrier->get_future();
803
+ get_multi_replicas_from_preferred_server_group(
804
+ specs,
805
+ options,
806
+ [barrier](
807
+ error err,
808
+ std::optional<
809
+ couchbase::transactions::transaction_get_multi_replicas_from_preferred_server_group_result>
810
+ res) {
811
+ return barrier->set_value(std::make_pair(std::move(err), std::move(res)));
812
+ });
813
+ return f.get();
814
+ }
815
+
523
816
  auto
524
817
  attempt_context_impl::create_document_metadata(
525
818
  const std::string& operation_type,
@@ -576,8 +869,8 @@ attempt_context_impl::create_document_metadata(
576
869
  }),
577
870
  });
578
871
  txn["fc"] = {
579
- { to_string(forward_compat_stage::WWC_INSERTING), fc_check },
580
- { to_string(forward_compat_stage::WWC_INSERTING_GET), fc_check },
872
+ { to_string(forward_compat_stage::WRITE_WRITE_CONFLICT_INSERTING), fc_check },
873
+ { to_string(forward_compat_stage::WRITE_WRITE_CONFLICT_INSERTING_GET), fc_check },
581
874
  { to_string(forward_compat_stage::GETS), fc_check },
582
875
  { to_string(forward_compat_stage::CLEANUP_ENTRY), fc_check },
583
876
  };
@@ -637,7 +930,7 @@ attempt_context_impl::replace(const transaction_get_result& document,
637
930
 
638
931
  self->check_and_handle_blocking_transactions(
639
932
  document,
640
- forward_compat_stage::WWC_REPLACING,
933
+ forward_compat_stage::WRITE_WRITE_CONFLICT_REPLACING,
641
934
  [self,
642
935
  existing_sm,
643
936
  document,
@@ -670,7 +963,7 @@ attempt_context_impl::replace(const transaction_get_result& document,
670
963
  self, "found existing INSERT of {} while replacing", document);
671
964
  self->create_staged_insert(document.id(),
672
965
  std::move(content),
673
- existing_sm->doc().cas().value(),
966
+ document.cas().value(),
674
967
  exp_delay(std::chrono::milliseconds(5),
675
968
  std::chrono::milliseconds(300),
676
969
  self->overall()->config().timeout),
@@ -678,7 +971,13 @@ attempt_context_impl::replace(const transaction_get_result& document,
678
971
  std::move(cb));
679
972
  return;
680
973
  }
681
- self->create_staged_replace(document, std::move(content), op_id, std::move(cb));
974
+ self->create_staged_replace(document.id(),
975
+ std::move(content),
976
+ document.content().flags,
977
+ document.cas(),
978
+ op_id,
979
+ document.metadata(),
980
+ std::move(cb));
682
981
  });
683
982
  });
684
983
  } catch (const client_error& e) {
@@ -701,6 +1000,9 @@ template<typename Response>
701
1000
  auto
702
1001
  external_exception_from_response(const Response& resp) -> external_exception
703
1002
  {
1003
+ if (resp.ctx.ec() == errc::key_value::document_irretrievable) {
1004
+ return DOCUMENT_UNRETRIEVABLE_EXCEPTION;
1005
+ }
704
1006
  if (const auto error_index = resp.ctx.first_error_index(); error_index) {
705
1007
  const auto status = resp.fields.at(error_index.value()).status;
706
1008
  const auto path = resp.fields.at(error_index.value()).path;
@@ -714,20 +1016,22 @@ external_exception_from_response(const Response& resp) -> external_exception
714
1016
 
715
1017
  template<typename Handler>
716
1018
  void
717
- attempt_context_impl::create_staged_replace(const transaction_get_result& document,
718
- codec::encoded_value content,
719
- const std::string& op_id,
720
- Handler&& cb)
1019
+ attempt_context_impl::create_staged_replace(
1020
+ const document_id& id,
1021
+ codec::encoded_value content,
1022
+ std::uint32_t original_flags,
1023
+ const couchbase::cas& cas,
1024
+ const std::string& op_id,
1025
+ const std::optional<document_metadata>& document_metadata,
1026
+ Handler&& cb)
721
1027
  {
722
- core::operations::mutate_in_request req{ document.id() };
1028
+ operations::mutate_in_request req{ id };
723
1029
  const bool binary =
724
1030
  codec::codec_flags::has_common_flags(content.flags, codec::codec_flags::binary_common_flags);
725
- auto txn = create_document_metadata("replace", op_id, document.metadata(), content.flags);
1031
+ auto txn = create_document_metadata("replace", op_id, document_metadata, content.flags);
726
1032
  req.specs =
727
1033
  mutate_in_specs{
728
- mutate_in_specs::upsert_raw("txn", core::utils::to_binary(jsonify(txn)))
729
- .xattr()
730
- .create_path(),
1034
+ mutate_in_specs::upsert_raw("txn", utils::to_binary(jsonify(txn))).xattr().create_path(),
731
1035
  mutate_in_specs::upsert_raw(binary ? "txn.op.bin" : "txn.op.stgd", content.data)
732
1036
  .xattr()
733
1037
  .binary(binary),
@@ -737,8 +1041,8 @@ attempt_context_impl::create_staged_replace(const transaction_get_result& docume
737
1041
  }
738
1042
  .specs();
739
1043
  req.durability_level = overall()->config().level;
740
- req.cas = document.cas();
741
- req.flags = document.content().flags;
1044
+ req.cas = cas;
1045
+ req.flags = original_flags;
742
1046
  req.access_deleted = true;
743
1047
  auto error_handler = [self = shared_from_this()](error_class ec,
744
1048
  external_exception cause,
@@ -759,25 +1063,26 @@ attempt_context_impl::create_staged_replace(const transaction_get_result& docume
759
1063
  return self->op_completed_with_error(std::forward<Handler>(cb), err);
760
1064
  }
761
1065
  };
762
- auto ec =
763
- wait_for_hook([self = shared_from_this(), key = document.id().key()](auto handler) mutable {
764
- return self->hooks_.before_staged_replace(self, key, std::move(handler));
765
- });
1066
+ auto ec = wait_for_hook([self = shared_from_this(), key = id.key()](auto handler) mutable {
1067
+ return self->hooks_.before_staged_replace(self, key, std::move(handler));
1068
+ });
766
1069
  if (ec) {
767
1070
  return error_handler(
768
1071
  *ec, UNKNOWN, "before_staged_replace hook raised error", std::forward<Handler>(cb));
769
1072
  }
770
1073
  CB_ATTEMPT_CTX_LOG_TRACE(this,
771
1074
  "about to replace doc {} with cas {} in txn {}",
772
- document.id(),
773
- document.cas().value(),
1075
+ id,
1076
+ cas.value(),
774
1077
  overall()->transaction_id());
775
1078
  overall()->cluster_ref().execute(
776
1079
  req,
777
1080
  [self = shared_from_this(),
778
- operation_id = op_id,
779
- document,
1081
+ op_id,
1082
+ id,
1083
+ document_metadata,
780
1084
  content = std::move(content),
1085
+ original_flags,
781
1086
  cb = std::forward<Handler>(cb),
782
1087
  error_handler = std::move(error_handler)](core::operations::mutate_in_response resp) mutable {
783
1088
  if (auto ec2 = error_class_from_response(resp); ec2) {
@@ -789,11 +1094,13 @@ attempt_context_impl::create_staged_replace(const transaction_get_result& docume
789
1094
  }
790
1095
  return self->hooks_.after_staged_replace_complete(
791
1096
  self,
792
- document.id().key(),
1097
+ id.key(),
793
1098
  [self,
794
- operation_id,
795
- document,
1099
+ op_id,
1100
+ id,
1101
+ document_metadata,
796
1102
  content = std::move(content),
1103
+ original_flags,
797
1104
  error_handler = std::move(error_handler),
798
1105
  cb = std::forward<Handler>(cb),
799
1106
  resp = std::move(resp)](auto ec) mutable {
@@ -808,23 +1115,23 @@ attempt_context_impl::create_staged_replace(const transaction_get_result& docume
808
1115
  std::optional<codec::encoded_value> staged_content_binary{};
809
1116
  if (codec::codec_flags::has_common_flags(content.flags,
810
1117
  codec::codec_flags::json_common_flags)) {
811
- staged_content_json = std::move(content);
1118
+ staged_content_json = content;
812
1119
  } else if (codec::codec_flags::has_common_flags(
813
1120
  content.flags, codec::codec_flags::binary_common_flags)) {
814
- staged_content_binary = std::move(content);
1121
+ staged_content_binary = content;
815
1122
  }
816
1123
  transaction_get_result out{
817
- document.id(),
818
- document.content(),
1124
+ id,
1125
+ content,
819
1126
  resp.cas.value(),
820
1127
  transaction_links{
821
1128
  self->atr_id_->key(),
822
- document.id().bucket(),
823
- document.id().scope(),
824
- document.id().collection(),
1129
+ id.bucket(),
1130
+ id.scope(),
1131
+ id.collection(),
825
1132
  self->overall()->transaction_id(),
826
1133
  self->id(),
827
- operation_id,
1134
+ op_id,
828
1135
  std::move(staged_content_json),
829
1136
  std::move(staged_content_binary),
830
1137
  std::nullopt,
@@ -835,25 +1142,50 @@ attempt_context_impl::create_staged_replace(const transaction_get_result& docume
835
1142
  std::nullopt,
836
1143
  false,
837
1144
  },
838
- document.metadata(),
1145
+ document_metadata,
839
1146
  };
840
1147
 
841
1148
  CB_ATTEMPT_CTX_LOG_TRACE(self, "replace staged content, result {}", out);
842
- self->staged_mutations_->add(staged_mutation{
843
- out,
844
- // TODO(SA): java SDK checks for
845
- // bucket_capability::subdoc_revive_document here
846
- out.links().staged_content_json_or_binary(),
847
- staged_mutation_type::REPLACE,
848
- });
849
- return self->op_completed_with_callback(std::forward<Handler>(cb), std::optional(out));
1149
+
1150
+ self->supports_replace_body_with_xattr(
1151
+ id.bucket(),
1152
+ [self,
1153
+ out = std::move(out),
1154
+ error_handler = std::move(error_handler),
1155
+ original_flags,
1156
+ cb = std::forward<Handler>(cb)](auto ec, bool supports) mutable {
1157
+ if (ec) {
1158
+ return error_handler(
1159
+ FAIL_OTHER,
1160
+ UNKNOWN,
1161
+ "failed to check whether replace_body_with_xattr is supported: " + ec.message(),
1162
+ std::forward<Handler>(cb));
1163
+ }
1164
+
1165
+ auto [staged_content, staged_flags] = out.links().staged_content_json_or_binary();
1166
+
1167
+ self->staged_mutations_->add(staged_mutation{
1168
+ staged_mutation_type::REPLACE,
1169
+ out.id(),
1170
+ out.cas(),
1171
+ supports ? std::nullopt
1172
+ : std::make_optional(staged_content), // We don't store the staged contents
1173
+ // if the cluster supports
1174
+ // replace_body_with_xattr
1175
+ staged_flags,
1176
+ original_flags,
1177
+ out.metadata(),
1178
+ });
1179
+ return self->op_completed_with_callback(std::forward<Handler>(cb),
1180
+ std::optional(out));
1181
+ });
850
1182
  });
851
1183
  });
852
1184
  }
853
1185
 
854
1186
  auto
855
- attempt_context_impl::replace(const transaction_get_result& document,
856
- codec::encoded_value content) -> transaction_get_result
1187
+ attempt_context_impl::replace(const transaction_get_result& document, codec::encoded_value content)
1188
+ -> transaction_get_result
857
1189
  {
858
1190
  auto barrier = std::make_shared<std::promise<transaction_get_result>>();
859
1191
  auto f = barrier->get_future();
@@ -920,8 +1252,8 @@ attempt_context_impl::insert_raw(const collection& coll,
920
1252
  }
921
1253
 
922
1254
  auto
923
- attempt_context_impl::insert(const core::document_id& id,
924
- codec::encoded_value content) -> transaction_get_result
1255
+ attempt_context_impl::insert(const core::document_id& id, codec::encoded_value content)
1256
+ -> transaction_get_result
925
1257
  {
926
1258
  auto barrier = std::make_shared<std::promise<transaction_get_result>>();
927
1259
  auto f = barrier->get_future();
@@ -981,8 +1313,13 @@ attempt_context_impl::insert(const core::document_id& id,
981
1313
  }
982
1314
  if (existing_sm != nullptr && existing_sm->type() == staged_mutation_type::REMOVE) {
983
1315
  CB_ATTEMPT_CTX_LOG_DEBUG(self, "found existing remove of {} while inserting", id);
984
- return self->create_staged_replace(
985
- existing_sm->doc(), std::move(content), op_id, std::move(cb));
1316
+ return self->create_staged_replace(existing_sm->id(),
1317
+ std::move(content),
1318
+ existing_sm->current_user_flags(),
1319
+ existing_sm->cas(),
1320
+ op_id,
1321
+ existing_sm->doc_metadata(),
1322
+ std::move(cb));
986
1323
  }
987
1324
  const std::uint64_t cas = 0;
988
1325
  self->create_staged_insert(id,
@@ -1076,8 +1413,8 @@ attempt_context_impl::check_atr_entry_for_blocking_document(const transaction_ge
1076
1413
  return e.attempt_id() == doc.links().staged_attempt_id();
1077
1414
  });
1078
1415
  if (it != entries.end()) {
1079
- auto fwd_err = check_forward_compat(forward_compat_stage::WWC_READING_ATR,
1080
- it->forward_compat());
1416
+ auto fwd_err = check_forward_compat(
1417
+ forward_compat_stage::WRITE_WRITE_CONFLICT_READING_ATR, it->forward_compat());
1081
1418
  if (fwd_err) {
1082
1419
  return cb(fwd_err);
1083
1420
  }
@@ -1168,7 +1505,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
1168
1505
  }
1169
1506
  return self->check_and_handle_blocking_transactions(
1170
1507
  document,
1171
- forward_compat_stage::WWC_REMOVING,
1508
+ forward_compat_stage::WRITE_WRITE_CONFLICT_REMOVING,
1172
1509
  [self, document, cb = std::move(cb), op_id, error_handler = std::move(error_handler)](
1173
1510
  std::optional<transaction_operation_failed> err1) mutable {
1174
1511
  if (err1) {
@@ -1252,11 +1589,16 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
1252
1589
  document.id(),
1253
1590
  resp.cas.value(),
1254
1591
  resp.ctx.ec().message());
1255
- // TODO(SA): this copy... can we do better?
1256
- transaction_get_result new_res = document;
1257
- new_res.cas(resp.cas.value());
1258
- self->staged_mutations_->add(
1259
- staged_mutation(new_res, {}, staged_mutation_type::REMOVE));
1592
+
1593
+ self->staged_mutations_->add(staged_mutation{
1594
+ staged_mutation_type::REMOVE,
1595
+ document.id(),
1596
+ resp.cas,
1597
+ {},
1598
+ document.content().flags,
1599
+ document.content().flags,
1600
+ document.metadata(),
1601
+ });
1260
1602
  return self->op_completed_with_callback(cb);
1261
1603
  });
1262
1604
  });
@@ -1413,11 +1755,11 @@ attempt_context_impl::query_begin_work(const std::optional<std::string>& query_c
1413
1755
  if (!staged_mutations_->empty()) {
1414
1756
  staged_mutations_->iterate([&mutations](staged_mutation& mut) {
1415
1757
  mutations.push_back(tao::json::value{
1416
- { "scp", mut.doc().id().scope() },
1417
- { "coll", mut.doc().id().collection() },
1418
- { "bkt", mut.doc().id().bucket() },
1419
- { "id", mut.doc().id().key() },
1420
- { "cas", std::to_string(mut.doc().cas().value()) },
1758
+ { "scp", mut.id().scope() },
1759
+ { "coll", mut.id().collection() },
1760
+ { "bkt", mut.id().bucket() },
1761
+ { "id", mut.id().key() },
1762
+ { "cas", std::to_string(mut.cas().value()) },
1421
1763
  { "type", mut.type_as_string() },
1422
1764
  });
1423
1765
  });
@@ -1797,11 +2139,25 @@ attempt_context_impl::do_public_query(
1797
2139
  try {
1798
2140
  auto result = do_core_query(statement, opts, query_context);
1799
2141
  auto [ctx, res] = core::impl::build_transaction_query_result(result);
1800
- return std::make_pair(core::impl::make_error(ctx), res);
2142
+ if (ctx.ec()) {
2143
+ // This should have already been converted to an exception by handle_query_error, which we
2144
+ // handle above. Let's return an error here just in case
2145
+ std::string msg = "Txns query error occured that should have been handled further upstream, "
2146
+ "which might indicate a bug.";
2147
+ if (std::holds_alternative<query_error_context>(ctx.cause())) {
2148
+ return { { ctx.ec(),
2149
+ std::move(msg),
2150
+ {},
2151
+ impl::make_error(std::get<query_error_context>(ctx.cause())) },
2152
+ {} };
2153
+ }
2154
+ return { { ctx.ec(), std::move(msg) }, {} };
2155
+ }
2156
+ return { {}, res };
1801
2157
  } catch (const transaction_operation_failed& e) {
1802
2158
  return { core::impl::make_error(e), {} };
1803
2159
  } catch (const op_exception& qe) {
1804
- return { core::impl::make_error(qe.ctx()), {} };
2160
+ return { core::impl::make_error(qe), {} };
1805
2161
  } catch (...) {
1806
2162
  // should not be necessary, but just in case...
1807
2163
  return { { couchbase::errc::transaction_op::generic }, {} };
@@ -1809,8 +2165,8 @@ attempt_context_impl::do_public_query(
1809
2165
  }
1810
2166
 
1811
2167
  auto
1812
- make_params(const core::document_id& id,
1813
- std::optional<codec::encoded_value> content) -> std::vector<core::json_string>
2168
+ make_params(const core::document_id& id, std::optional<codec::encoded_value> content)
2169
+ -> std::vector<core::json_string>
1814
2170
  {
1815
2171
  if (content && !codec::codec_flags::has_common_flags(content->flags,
1816
2172
  codec::codec_flags::json_common_flags)) {
@@ -1925,7 +2281,7 @@ attempt_context_impl::insert_raw_with_query(const core::document_id& id,
1925
2281
  std::rethrow_exception(err);
1926
2282
  } catch (const transaction_operation_failed& e) {
1927
2283
  return self->op_completed_with_error(std::move(cb), e);
1928
- } catch (const document_exists& ex) {
2284
+ } catch (const op_exception& ex) {
1929
2285
  return self->op_completed_with_error(std::move(cb), ex);
1930
2286
  } catch (const std::exception& e) {
1931
2287
  return self->op_completed_with_error(
@@ -2939,20 +3295,32 @@ attempt_context_impl::do_get(const core::document_id& id,
2939
3295
  {
2940
3296
  try {
2941
3297
  if (check_expiry_pre_commit(STAGE_GET, id.key())) {
2942
- return cb(FAIL_EXPIRY, "expired in do_get", std::nullopt);
3298
+ return cb(FAIL_EXPIRY, std::nullopt, "expired in do_get", std::nullopt);
2943
3299
  }
2944
3300
 
3301
+ // Check if we already have a staged insert/replace for this document AND we have the content
3302
+ // for it (i.e. the cluster does not support replace body_with_xattr)
2945
3303
  if (const staged_mutation* own_write = check_for_own_write(id); own_write != nullptr) {
2946
- CB_ATTEMPT_CTX_LOG_DEBUG(this, "found own-write of mutated doc {}", id);
2947
- return cb(std::nullopt,
2948
- std::nullopt,
2949
- transaction_get_result::create_from(own_write->doc(), own_write->content()));
3304
+ const auto own_write_content = own_write->staged_content();
3305
+ if (own_write_content.has_value()) {
3306
+ CB_ATTEMPT_CTX_LOG_DEBUG(this, "found own-write of mutated doc {}", id);
3307
+ return cb(std::nullopt,
3308
+ std::nullopt,
3309
+ std::nullopt,
3310
+ transaction_get_result{
3311
+ own_write->id(),
3312
+ codec::encoded_value{ own_write_content.value(), own_write->staged_flags() },
3313
+ own_write->cas().value(),
3314
+ {},
3315
+ {},
3316
+ });
3317
+ }
2950
3318
  }
2951
3319
  if (const staged_mutation* own_remove = staged_mutations_->find_remove(id);
2952
3320
  own_remove != nullptr) {
2953
3321
  auto msg = fmt::format("found own-write of removed doc {}", id);
2954
3322
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "{}", msg);
2955
- return cb(FAIL_DOC_NOT_FOUND, msg, std::nullopt);
3323
+ return cb(FAIL_DOC_NOT_FOUND, std::nullopt, msg, std::nullopt);
2956
3324
  }
2957
3325
 
2958
3326
  return hooks_.before_doc_get(
@@ -2964,7 +3332,7 @@ attempt_context_impl::do_get(const core::document_id& id,
2964
3332
  resolving_missing_atr_entry = std::move(resolving_missing_atr_entry),
2965
3333
  cb = std::forward<Handler>(cb)](auto ec) mutable {
2966
3334
  if (ec) {
2967
- return cb(ec, "before_doc_get hook raised error", std::nullopt);
3335
+ return cb(ec, std::nullopt, "before_doc_get hook raised error", std::nullopt);
2968
3336
  }
2969
3337
 
2970
3338
  return self->get_doc(
@@ -2975,115 +3343,133 @@ attempt_context_impl::do_get(const core::document_id& id,
2975
3343
  allow_replica,
2976
3344
  resolving_missing_atr_entry = std::move(resolving_missing_atr_entry),
2977
3345
  cb = std::move(cb)](std::optional<error_class> ec,
3346
+ std::optional<external_exception> cause,
2978
3347
  const std::optional<std::string>& err_message,
2979
3348
  std::optional<transaction_get_result> doc) mutable {
2980
3349
  if (!ec && !doc) {
2981
3350
  // it just isn't there.
2982
- return cb(std::nullopt, std::nullopt, std::nullopt);
3351
+ return cb(std::nullopt, std::nullopt, std::nullopt, std::nullopt);
2983
3352
  }
2984
- if (!ec) {
2985
- if (doc->links().is_document_in_transaction()) {
3353
+
3354
+ if (ec) {
3355
+ return cb(ec, cause, err_message, std::nullopt);
3356
+ }
3357
+
3358
+ if (!doc->links().is_document_in_transaction()) {
3359
+ if (doc->links().is_deleted()) {
2986
3360
  CB_ATTEMPT_CTX_LOG_DEBUG(self,
2987
- "doc {} in transaction, resolving_missing_atr_entry={}",
2988
- *doc,
2989
- resolving_missing_atr_entry.value_or("-"));
2990
-
2991
- if (resolving_missing_atr_entry.has_value() &&
2992
- resolving_missing_atr_entry.value() == doc->links().staged_attempt_id()) {
2993
- CB_ATTEMPT_CTX_LOG_DEBUG(self, "doc is in lost pending transaction");
2994
-
2995
- if (doc->links().is_document_being_inserted()) {
2996
- // this document is being inserted, so should not be visible
2997
- // yet
2998
- return cb(std::nullopt, std::nullopt, std::nullopt);
2999
- }
3361
+ "doc not in txn, and is_deleted, so not returning it.");
3362
+ // doc has been deleted, not in txn, so don't return it
3363
+ return cb(std::nullopt, std::nullopt, std::nullopt, std::nullopt);
3364
+ }
3365
+ return cb(std::nullopt, std::nullopt, std::nullopt, doc);
3366
+ }
3000
3367
 
3001
- return cb(std::nullopt, std::nullopt, doc);
3002
- }
3368
+ if (doc->links().staged_attempt_id() == self->id()) {
3369
+ // This is a RYOW, and we can optimise here by not looking up the document's ATR.
3003
3370
 
3004
- const core::document_id doc_atr_id{ doc->links().atr_bucket_name().value(),
3005
- doc->links().atr_scope_name().value(),
3006
- doc->links().atr_collection_name().value(),
3007
- doc->links().atr_id().value() };
3008
- active_transaction_record::get_atr(
3009
- self->cluster_ref(),
3010
- doc_atr_id,
3011
- [self, id, allow_replica, doc, cb = std::move(cb)](
3012
- std::error_code ec2, std::optional<active_transaction_record> atr) mutable {
3013
- if (!ec2 && atr) {
3014
- const active_transaction_record& atr_doc = atr.value();
3015
- std::optional<atr_entry> entry;
3016
- for (const auto& e : atr_doc.entries()) {
3017
- if (doc->links().staged_attempt_id().value() == e.attempt_id()) {
3018
- entry.emplace(e);
3019
- break;
3020
- }
3371
+ if (doc->links().is_document_being_removed()) {
3372
+ // The document is being removed by this attempt, return empty.
3373
+ return cb(std::nullopt, std::nullopt, std::nullopt, std::nullopt);
3374
+ }
3375
+
3376
+ // Return the post-transaction version.
3377
+ return cb(std::nullopt,
3378
+ std::nullopt,
3379
+ std::nullopt,
3380
+ transaction_get_result::create_from(
3381
+ *doc, doc->links().staged_content_json_or_binary()));
3382
+ }
3383
+
3384
+ CB_ATTEMPT_CTX_LOG_DEBUG(self,
3385
+ "doc {} in transaction, resolving_missing_atr_entry={}",
3386
+ *doc,
3387
+ resolving_missing_atr_entry.value_or("-"));
3388
+
3389
+ if (resolving_missing_atr_entry.has_value() &&
3390
+ resolving_missing_atr_entry.value() == doc->links().staged_attempt_id()) {
3391
+ CB_ATTEMPT_CTX_LOG_DEBUG(self, "doc is in lost pending transaction");
3392
+
3393
+ if (doc->links().is_document_being_inserted()) {
3394
+ // this document is being inserted, so should not be visible
3395
+ // yet
3396
+ return cb(std::nullopt, std::nullopt, std::nullopt, std::nullopt);
3397
+ }
3398
+
3399
+ return cb(std::nullopt, std::nullopt, std::nullopt, doc);
3400
+ }
3401
+
3402
+ const core::document_id doc_atr_id{ doc->links().atr_bucket_name().value(),
3403
+ doc->links().atr_scope_name().value(),
3404
+ doc->links().atr_collection_name().value(),
3405
+ doc->links().atr_id().value() };
3406
+ active_transaction_record::get_atr(
3407
+ self->cluster_ref(),
3408
+ doc_atr_id,
3409
+ [self, id, allow_replica, doc, cb = std::move(cb)](
3410
+ std::error_code ec2, std::optional<active_transaction_record> atr) mutable {
3411
+ if (!ec2 && atr) {
3412
+ const active_transaction_record& atr_doc = atr.value();
3413
+ std::optional<atr_entry> entry;
3414
+ for (const auto& e : atr_doc.entries()) {
3415
+ if (doc->links().staged_attempt_id().value() == e.attempt_id()) {
3416
+ entry.emplace(e);
3417
+ break;
3418
+ }
3419
+ }
3420
+ bool ignore_doc = false;
3421
+ auto content = doc->content();
3422
+ if (entry) {
3423
+ if (doc->links().staged_attempt_id() && entry->attempt_id() == self->id()) {
3424
+ // Attempt is reading its own writes
3425
+ // This is here as backup, it should be returned
3426
+ // from the in-memory cache instead
3427
+ content = doc->links().staged_content_json_or_binary();
3428
+ } else {
3429
+ auto err = check_forward_compat(forward_compat_stage::GETS_READING_ATR,
3430
+ entry->forward_compat());
3431
+ if (err) {
3432
+ return cb(FAIL_OTHER, err->cause(), err->what(), std::nullopt);
3021
3433
  }
3022
- bool ignore_doc = false;
3023
- auto content = doc->content();
3024
- if (entry) {
3025
- if (doc->links().staged_attempt_id() && entry->attempt_id() == self->id()) {
3026
- // Attempt is reading its own writes
3027
- // This is here as backup, it should be returned
3028
- // from the in-memory cache instead
3029
- content = doc->links().staged_content_json_or_binary();
3030
- } else {
3031
- auto err = check_forward_compat(forward_compat_stage::GETS_READING_ATR,
3032
- entry->forward_compat());
3033
- if (err) {
3034
- return cb(FAIL_OTHER, err->what(), std::nullopt);
3434
+ switch (entry->state()) {
3435
+ case attempt_state::COMPLETED:
3436
+ case attempt_state::COMMITTED:
3437
+ if (doc->links().is_document_being_removed()) {
3438
+ ignore_doc = true;
3439
+ } else {
3440
+ content = doc->links().staged_content_json_or_binary();
3035
3441
  }
3036
- switch (entry->state()) {
3037
- case attempt_state::COMPLETED:
3038
- case attempt_state::COMMITTED:
3039
- if (doc->links().is_document_being_removed()) {
3040
- ignore_doc = true;
3041
- } else {
3042
- content = doc->links().staged_content_json_or_binary();
3043
- }
3044
- break;
3045
- default:
3046
- if (doc->links().is_document_being_inserted()) {
3047
- // This document is being inserted, so should
3048
- // not be visible yet
3049
- ignore_doc = true;
3050
- }
3051
- break;
3442
+ break;
3443
+ default:
3444
+ if (doc->links().is_document_being_inserted()) {
3445
+ // This document is being inserted, so should
3446
+ // not be visible yet
3447
+ ignore_doc = true;
3052
3448
  }
3053
- }
3054
- } else {
3055
- // failed to get the ATR entry
3056
- CB_ATTEMPT_CTX_LOG_DEBUG(self,
3057
- "could not get ATR entry, checking again with {}",
3058
- doc->links().staged_attempt_id().value_or("-"));
3059
- return self->do_get(
3060
- id, allow_replica, doc->links().staged_attempt_id(), cb);
3061
- }
3062
- if (ignore_doc) {
3063
- return cb(std::nullopt, std::nullopt, std::nullopt);
3449
+ break;
3064
3450
  }
3065
- return cb(std::nullopt,
3066
- std::nullopt,
3067
- transaction_get_result::create_from(*doc, content));
3068
3451
  }
3069
- // failed to get the ATR
3452
+ } else {
3453
+ // failed to get the ATR entry
3070
3454
  CB_ATTEMPT_CTX_LOG_DEBUG(self,
3071
- "could not get ATR, checking again with {}",
3455
+ "could not get ATR entry, checking again with {}",
3072
3456
  doc->links().staged_attempt_id().value_or("-"));
3073
3457
  return self->do_get(id, allow_replica, doc->links().staged_attempt_id(), cb);
3074
- });
3075
- } else {
3076
- if (doc->links().is_deleted()) {
3077
- CB_ATTEMPT_CTX_LOG_DEBUG(self,
3078
- "doc not in txn, and is_deleted, so not returning it.");
3079
- // doc has been deleted, not in txn, so don't return it
3080
- return cb(std::nullopt, std::nullopt, std::nullopt);
3458
+ }
3459
+ if (ignore_doc) {
3460
+ return cb(std::nullopt, std::nullopt, std::nullopt, std::nullopt);
3461
+ }
3462
+ return cb(std::nullopt,
3463
+ std::nullopt,
3464
+ std::nullopt,
3465
+ transaction_get_result::create_from(*doc, content));
3081
3466
  }
3082
- return cb(std::nullopt, std::nullopt, doc);
3083
- }
3084
- } else {
3085
- return cb(ec, err_message, std::nullopt);
3086
- }
3467
+ // failed to get the ATR
3468
+ CB_ATTEMPT_CTX_LOG_DEBUG(self,
3469
+ "could not get ATR, checking again with {}",
3470
+ doc->links().staged_attempt_id().value_or("-"));
3471
+ return self->do_get(id, allow_replica, doc->links().staged_attempt_id(), cb);
3472
+ });
3087
3473
  });
3088
3474
  });
3089
3475
  } catch (const transaction_operation_failed&) {
@@ -3108,12 +3494,17 @@ execute_lookup(attempt_context_impl* ctx, LookupInRequest& req, Callback&& cb)
3108
3494
  CB_ATTEMPT_CTX_LOG_TRACE(ctx, "get_doc got error {} : {}", resp.ctx.ec().message(), *ec);
3109
3495
  switch (*ec) {
3110
3496
  case FAIL_PATH_NOT_FOUND:
3111
- return cb(ec, resp.ctx.ec().message(), transaction_get_result::create_from(resp));
3497
+ return cb(ec,
3498
+ external_exception_from_response(resp),
3499
+ resp.ctx.ec().message(),
3500
+ transaction_get_result::create_from(resp));
3112
3501
  default:
3113
- return cb(ec, resp.ctx.ec().message(), std::nullopt);
3502
+ return cb(
3503
+ ec, external_exception_from_response(resp), resp.ctx.ec().message(), std::nullopt);
3114
3504
  }
3115
3505
  } else {
3116
- return cb({}, {}, transaction_get_result::create_from(resp));
3506
+ return cb(
3507
+ std::nullopt, std::nullopt, std::nullopt, transaction_get_result::create_from(resp));
3117
3508
  }
3118
3509
  });
3119
3510
  }
@@ -3123,6 +3514,7 @@ void
3123
3514
  attempt_context_impl::get_doc(const core::document_id& id,
3124
3515
  bool allow_replica,
3125
3516
  std::function<void(std::optional<error_class>,
3517
+ std::optional<external_exception>,
3126
3518
  std::optional<std::string>,
3127
3519
  std::optional<transaction_get_result>)>&& cb)
3128
3520
  {
@@ -3145,7 +3537,7 @@ attempt_context_impl::get_doc(const core::document_id& id,
3145
3537
  try {
3146
3538
  if (allow_replica) {
3147
3539
  core::operations::lookup_in_any_replica_request req{ id };
3148
- req.read_preference = couchbase::read_preference::selected_server_group;
3540
+ req.read_preference = couchbase::read_preference::selected_server_group_or_all_available;
3149
3541
  req.specs = specs;
3150
3542
  execute_lookup(this, req, cb);
3151
3543
  } else {
@@ -3155,7 +3547,7 @@ attempt_context_impl::get_doc(const core::document_id& id,
3155
3547
  execute_lookup(this, req, cb);
3156
3548
  }
3157
3549
  } catch (const std::exception& e) {
3158
- return cb(FAIL_OTHER, e.what(), std::nullopt);
3550
+ return cb(FAIL_OTHER, std::nullopt, e.what(), std::nullopt);
3159
3551
  }
3160
3552
  }
3161
3553
 
@@ -3252,6 +3644,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
3252
3644
  false,
3253
3645
  [self, id, content, op_id, cb = std::forward<Handler>(cb), error_handler, delay](
3254
3646
  std::optional<error_class> ec3,
3647
+ std::optional<external_exception> /* cause */,
3255
3648
  std::optional<std::string> err_message,
3256
3649
  std::optional<transaction_get_result> doc) mutable {
3257
3650
  if (!ec3) {
@@ -3263,8 +3656,9 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
3263
3656
  doc->links().is_document_in_transaction(),
3264
3657
  doc->links().is_deleted());
3265
3658
 
3266
- if (auto err = check_forward_compat(forward_compat_stage::WWC_INSERTING_GET,
3267
- doc->links().forward_compat());
3659
+ if (auto err = check_forward_compat(
3660
+ forward_compat_stage::WRITE_WRITE_CONFLICT_INSERTING_GET,
3661
+ doc->links().forward_compat());
3268
3662
  err) {
3269
3663
  return self->op_completed_with_error(std::forward<Handler>(cb), *err);
3270
3664
  }
@@ -3293,8 +3687,15 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
3293
3687
  // this is us dealing with resolving an ambiguity. So, lets
3294
3688
  // just update the staged_mutation with the correct cas and
3295
3689
  // continue...
3296
- self->staged_mutations_->add(
3297
- staged_mutation(*doc, content, staged_mutation_type::INSERT));
3690
+ self->staged_mutations_->add(staged_mutation{
3691
+ staged_mutation_type::INSERT,
3692
+ doc->id(),
3693
+ doc->cas(),
3694
+ content.data,
3695
+ content.flags,
3696
+ doc->content().flags,
3697
+ doc->metadata(),
3698
+ });
3298
3699
  return self->op_completed_with_callback(std::forward<Handler>(cb), doc);
3299
3700
  }
3300
3701
  return self->op_completed_with_error(
@@ -3313,7 +3714,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
3313
3714
  }
3314
3715
  self->check_and_handle_blocking_transactions(
3315
3716
  *doc,
3316
- forward_compat_stage::WWC_INSERTING,
3717
+ forward_compat_stage::WRITE_WRITE_CONFLICT_INSERTING,
3317
3718
  [self, id, op_id, content, doc, cb = std::forward<Handler>(cb), delay](
3318
3719
  std::optional<transaction_operation_failed> err) mutable {
3319
3720
  if (err) {
@@ -3459,55 +3860,89 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
3459
3860
 
3460
3861
  CB_ATTEMPT_CTX_LOG_DEBUG(
3461
3862
  self, "inserted doc {} CAS={}, {}", id, resp.cas.value(), resp.ctx.ec().message());
3462
- std::optional<codec::encoded_value> staged_content_json{};
3463
- std::optional<codec::encoded_value> staged_content_binary{};
3464
- if (codec::codec_flags::has_common_flags(content.flags,
3465
- codec::codec_flags::json_common_flags)) {
3466
- staged_content_json = std::move(content);
3467
- } else if (codec::codec_flags::has_common_flags(
3468
- content.flags, codec::codec_flags::binary_common_flags)) {
3469
- staged_content_binary = std::move(content);
3470
- }
3471
- transaction_get_result out{
3472
- id,
3473
- {},
3474
- resp.cas.value(),
3475
- transaction_links{
3476
- self->atr_id_->key(),
3477
- id.bucket(),
3478
- id.scope(),
3479
- id.collection(),
3480
- self->overall()->transaction_id(),
3481
- self->id(),
3482
- op_id,
3483
- std::move(staged_content_json),
3484
- std::move(staged_content_binary),
3485
- std::nullopt,
3486
- std::nullopt,
3487
- std::nullopt,
3488
- std::nullopt,
3489
- "insert",
3490
- std::nullopt,
3491
- true,
3492
- },
3493
- std::nullopt,
3494
- };
3495
- self->staged_mutations_->add(staged_mutation{
3496
- out,
3497
- // TODO(SA): java SDK checks for
3498
- // bucket_capability::subdoc_revive_document here
3499
- out.links().staged_content_json_or_binary(),
3500
- staged_mutation_type::INSERT,
3501
- });
3502
- return self->op_completed_with_callback(std::forward<Handler>(cb),
3503
- std::optional{ std::move(out) });
3863
+
3864
+ self->supports_replace_body_with_xattr(
3865
+ id.bucket(),
3866
+ [self,
3867
+ id,
3868
+ content = std::move(content),
3869
+ cas,
3870
+ op_id,
3871
+ delay = std::forward<Delay>(delay),
3872
+ resp = std::move(resp),
3873
+ cb = std::forward<Handler>(cb)](auto ec, bool supports) mutable {
3874
+ if (ec) {
3875
+ return self->create_staged_insert_error_handler(
3876
+ id,
3877
+ std::move(content),
3878
+ cas,
3879
+ std::forward<Delay>(delay),
3880
+ op_id,
3881
+ std::forward<Handler>(cb),
3882
+ FAIL_OTHER,
3883
+ UNKNOWN,
3884
+ "failed to check whether replace_body_with_xattr is supported: " + ec.message());
3885
+ }
3886
+
3887
+ std::optional<codec::encoded_value> staged_content_json{};
3888
+ std::optional<codec::encoded_value> staged_content_binary{};
3889
+ if (codec::codec_flags::has_common_flags(content.flags,
3890
+ codec::codec_flags::json_common_flags)) {
3891
+ staged_content_json = content;
3892
+ } else if (codec::codec_flags::has_common_flags(
3893
+ content.flags, codec::codec_flags::binary_common_flags)) {
3894
+ staged_content_binary = content;
3895
+ }
3896
+
3897
+ transaction_get_result out{
3898
+ id,
3899
+ content,
3900
+ resp.cas.value(),
3901
+ transaction_links{
3902
+ self->atr_id_->key(),
3903
+ id.bucket(),
3904
+ id.scope(),
3905
+ id.collection(),
3906
+ self->overall()->transaction_id(),
3907
+ self->id(),
3908
+ op_id,
3909
+ std::move(staged_content_json),
3910
+ std::move(staged_content_binary),
3911
+ std::nullopt,
3912
+ std::nullopt,
3913
+ std::nullopt,
3914
+ std::nullopt,
3915
+ "insert",
3916
+ std::nullopt,
3917
+ true,
3918
+ },
3919
+ std::nullopt,
3920
+ };
3921
+
3922
+ auto [staged_content, staged_flags] = out.links().staged_content_json_or_binary();
3923
+
3924
+ self->staged_mutations_->add(staged_mutation{
3925
+ staged_mutation_type::INSERT,
3926
+ id,
3927
+ resp.cas,
3928
+ supports ? std::nullopt
3929
+ : std::make_optional(staged_content), // We don't store the staged contents
3930
+ // if the cluster supports
3931
+ // replace_body_with_xattr
3932
+ staged_flags,
3933
+ staged_flags,
3934
+ out.metadata(),
3935
+ });
3936
+ return self->op_completed_with_callback(std::forward<Handler>(cb),
3937
+ std::optional{ std::move(out) });
3938
+ });
3504
3939
  });
3505
3940
  });
3506
3941
  }
3507
3942
 
3508
3943
  void
3509
3944
  attempt_context_impl::ensure_open_bucket(const std::string& bucket_name,
3510
- std::function<void(std::error_code)>&& handler)
3945
+ std::function<void(std::error_code)>&& handler) const
3511
3946
  {
3512
3947
  if (bucket_name.empty()) {
3513
3948
  CB_LOG_DEBUG("ensure_open_bucket called with empty bucket_name");
@@ -3518,6 +3953,29 @@ attempt_context_impl::ensure_open_bucket(const std::string& bucket_name,
3518
3953
  });
3519
3954
  }
3520
3955
 
3956
+ void
3957
+ attempt_context_impl::supports_replace_body_with_xattr(
3958
+ const std::string& bucket_name,
3959
+ std::function<void(std::error_code, bool)>&& handler) const
3960
+ {
3961
+ cluster_ref().with_bucket_configuration(
3962
+ bucket_name,
3963
+ [handler = std::move(handler)](std::error_code ec,
3964
+ const std::shared_ptr<topology::configuration>& config) {
3965
+ if (ec) {
3966
+ handler(ec, {});
3967
+ return;
3968
+ }
3969
+
3970
+ // We use subdoc_revive_document instead of subdoc_replace_body_with_xattr.
3971
+ // The version where subdoc_replace_body_with_xattr was released (7.0) contained a significant
3972
+ // bug.
3973
+ // (https://github.com/couchbaselabs/couchbase-transactions-specs/blob/master/transactions-stages.md#supportsreplacebodywithxattr)
3974
+ handler(
3975
+ {}, config->capabilities.has_bucket_capability(bucket_capability::subdoc_revive_document));
3976
+ });
3977
+ }
3978
+
3521
3979
  void
3522
3980
  attempt_context_impl::remove(couchbase::transactions::transaction_get_result doc,
3523
3981
  couchbase::transactions::async_err_handler&& handler)
@@ -3585,14 +4043,28 @@ attempt_context_impl::query(std::string statement,
3585
4043
  } catch (const transaction_operation_failed& e) {
3586
4044
  return handler(core::impl::make_error(e), {});
3587
4045
  } catch (const op_exception& ex) {
3588
- return handler(core::impl::make_error(ex.ctx()), {});
4046
+ return handler(core::impl::make_error(ex), {});
3589
4047
  } catch (...) {
3590
4048
  // just in case...
3591
4049
  return handler({ couchbase::errc::transaction_op::generic }, {});
3592
4050
  }
3593
4051
  }
3594
4052
  auto [ctx, res] = core::impl::build_transaction_query_result(*resp);
3595
- handler(core::impl::make_error(ctx), res);
4053
+ if (ctx.ec()) {
4054
+ // This should have already been converted to an exception by handle_query_error,
4055
+ // which we handle above. Let's return an error here just in case
4056
+ std::string msg = "Txns query error occured that should have been handled further "
4057
+ "upstream, which might indicate a bug.";
4058
+ if (std::holds_alternative<query_error_context>(ctx.cause())) {
4059
+ return handler({ ctx.ec(),
4060
+ std::move(msg),
4061
+ {},
4062
+ impl::make_error(std::get<query_error_context>(ctx.cause())) },
4063
+ {});
4064
+ }
4065
+ return handler({ ctx.ec(), std::move(msg) }, {});
4066
+ }
4067
+ return handler({}, res);
3596
4068
  });
3597
4069
  }
3598
4070
 
@@ -3729,4 +4201,10 @@ attempt_context_impl::atr_collection_name(const std::string& coll) const
3729
4201
  {
3730
4202
  overall()->atr_collection(coll);
3731
4203
  }
4204
+
4205
+ auto
4206
+ attempt_context_impl::expiry_time() const -> std::chrono::steady_clock::time_point
4207
+ {
4208
+ return overall()->expiry_time();
4209
+ }
3732
4210
  } // namespace couchbase::core::transactions