couchbase 3.5.6 → 3.6.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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/cache/extconf_include.rb +3 -3
  4. data/ext/cache/mozilla-ca-bundle.crt +3 -165
  5. data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
  6. data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/CMakeLists.txt +14 -10
  7. data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy.cc +7 -4
  8. data/ext/couchbase/CMakeLists.txt +12 -1
  9. data/ext/couchbase/cmake/Profiler.cmake +15 -0
  10. data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +2 -2
  11. data/ext/couchbase/cmake/couchbase_cxx_client.pc.in +1 -1
  12. data/ext/couchbase/core/app_telemetry_address.cxx +55 -0
  13. data/ext/couchbase/core/app_telemetry_address.hxx +39 -0
  14. data/ext/couchbase/core/app_telemetry_meter.cxx +753 -0
  15. data/ext/couchbase/core/app_telemetry_meter.hxx +198 -0
  16. data/ext/couchbase/core/app_telemetry_reporter.cxx +895 -0
  17. data/ext/couchbase/core/app_telemetry_reporter.hxx +59 -0
  18. data/ext/couchbase/core/bucket.cxx +77 -35
  19. data/ext/couchbase/core/bucket.hxx +17 -10
  20. data/ext/couchbase/core/cluster.cxx +54 -16
  21. data/ext/couchbase/core/cluster_credentials.cxx +27 -0
  22. data/ext/couchbase/core/cluster_credentials.hxx +36 -0
  23. data/ext/couchbase/core/cluster_options.hxx +12 -0
  24. data/ext/couchbase/core/collections_component.cxx +7 -5
  25. data/ext/couchbase/core/http_component.cxx +6 -0
  26. data/ext/couchbase/core/impl/binary_collection.cxx +4 -0
  27. data/ext/couchbase/core/impl/bucket_manager.cxx +2 -0
  28. data/ext/couchbase/core/impl/cluster.cxx +9 -0
  29. data/ext/couchbase/core/impl/collection.cxx +2 -0
  30. data/ext/couchbase/core/impl/error.cxx +1 -0
  31. data/ext/couchbase/core/impl/logger.cxx +51 -0
  32. data/ext/couchbase/core/impl/replica_utils.cxx +1 -1
  33. data/ext/couchbase/core/impl/transaction_get_multi_replicas_from_preferred_server_group_spec.cxx +32 -0
  34. data/ext/couchbase/core/impl/transaction_get_multi_spec.cxx +30 -0
  35. data/ext/couchbase/core/impl/transaction_op_error_category.cxx +2 -0
  36. data/ext/couchbase/core/io/config_tracker.cxx +6 -6
  37. data/ext/couchbase/core/io/http_command.hxx +35 -11
  38. data/ext/couchbase/core/io/http_session.cxx +10 -0
  39. data/ext/couchbase/core/io/http_session.hxx +4 -0
  40. data/ext/couchbase/core/io/http_session_manager.hxx +83 -34
  41. data/ext/couchbase/core/io/mcbp_command.hxx +41 -2
  42. data/ext/couchbase/core/io/mcbp_session.cxx +52 -19
  43. data/ext/couchbase/core/io/mcbp_session.hxx +3 -0
  44. data/ext/couchbase/core/logger/logger.cxx +46 -0
  45. data/ext/couchbase/core/logger/logger.hxx +41 -1
  46. data/ext/couchbase/core/management/bucket_settings.hxx +1 -0
  47. data/ext/couchbase/core/management/bucket_settings_json.hxx +4 -0
  48. data/ext/couchbase/core/meta/features.hxx +32 -0
  49. data/ext/couchbase/core/operations/document_analytics.cxx +9 -9
  50. data/ext/couchbase/core/operations/document_append.cxx +1 -0
  51. data/ext/couchbase/core/operations/document_append.hxx +1 -0
  52. data/ext/couchbase/core/operations/document_get_all_replicas.hxx +10 -2
  53. data/ext/couchbase/core/operations/document_lookup_in.cxx +4 -0
  54. data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +14 -2
  55. data/ext/couchbase/core/operations/document_lookup_in_any_replica.hxx +4 -0
  56. data/ext/couchbase/core/operations/document_mutate_in.cxx +4 -0
  57. data/ext/couchbase/core/operations/document_mutate_in.hxx +1 -0
  58. data/ext/couchbase/core/operations/document_prepend.cxx +1 -0
  59. data/ext/couchbase/core/operations/document_prepend.hxx +1 -0
  60. data/ext/couchbase/core/operations/document_query.cxx +12 -10
  61. data/ext/couchbase/core/operations/http_noop.cxx +1 -0
  62. data/ext/couchbase/core/operations/management/bucket_create.cxx +3 -0
  63. data/ext/couchbase/core/operations/management/bucket_update.cxx +3 -0
  64. data/ext/couchbase/core/origin.cxx +0 -5
  65. data/ext/couchbase/core/origin.hxx +2 -11
  66. data/ext/couchbase/core/platform/random.cc +6 -3
  67. data/ext/couchbase/core/platform/random.h +2 -2
  68. data/ext/couchbase/core/protocol/cmd_mutate_in.hxx +9 -0
  69. data/ext/couchbase/core/timeout_defaults.hxx +4 -0
  70. data/ext/couchbase/core/topology/configuration.cxx +10 -13
  71. data/ext/couchbase/core/topology/configuration.hxx +14 -15
  72. data/ext/couchbase/core/topology/configuration_json.hxx +6 -0
  73. data/ext/couchbase/core/transactions/async_attempt_context.hxx +22 -2
  74. data/ext/couchbase/core/transactions/attempt_context.hxx +25 -7
  75. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +688 -238
  76. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +91 -12
  77. data/ext/couchbase/core/transactions/exceptions.cxx +5 -0
  78. data/ext/couchbase/core/transactions/exceptions.hxx +20 -0
  79. data/ext/couchbase/core/transactions/exceptions_fmt.hxx +3 -0
  80. data/ext/couchbase/core/transactions/forward_compat.cxx +71 -6
  81. data/ext/couchbase/core/transactions/forward_compat.hxx +45 -59
  82. data/ext/couchbase/core/transactions/get_multi_orchestrator.cxx +616 -0
  83. data/ext/couchbase/core/transactions/get_multi_orchestrator.hxx +61 -0
  84. data/ext/couchbase/core/transactions/internal/doc_record.cxx +8 -0
  85. data/ext/couchbase/core/transactions/internal/doc_record.hxx +16 -5
  86. data/ext/couchbase/core/transactions/internal/exceptions_internal.hxx +12 -0
  87. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +13 -0
  88. data/ext/couchbase/core/transactions/internal/transaction_fields.hxx +1 -0
  89. data/ext/couchbase/core/transactions/staged_mutation.cxx +277 -96
  90. data/ext/couchbase/core/transactions/staged_mutation.hxx +28 -76
  91. data/ext/couchbase/core/transactions/transaction_context.cxx +33 -0
  92. data/ext/couchbase/core/transactions/transaction_get_multi_mode.hxx +28 -0
  93. data/ext/couchbase/core/transactions/transaction_get_multi_replicas_from_preferred_server_group_mode.hxx +27 -0
  94. data/ext/couchbase/core/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +71 -0
  95. data/ext/couchbase/core/transactions/transaction_get_multi_result.hxx +66 -0
  96. data/ext/couchbase/core/transactions/transaction_links.hxx +10 -0
  97. data/ext/couchbase/core/transactions/transactions.cxx +8 -3
  98. data/ext/couchbase/core/utils/connection_string.cxx +4 -0
  99. data/ext/couchbase/core/utils/url_codec.cxx +26 -0
  100. data/ext/couchbase/core/utils/url_codec.hxx +11 -0
  101. data/ext/couchbase/core/websocket_codec.cxx +647 -0
  102. data/ext/couchbase/core/websocket_codec.hxx +77 -0
  103. data/ext/couchbase/couchbase/analytics_options.hxx +70 -6
  104. data/ext/couchbase/couchbase/application_telemetry_options.hxx +124 -0
  105. data/ext/couchbase/couchbase/cluster_options.hxx +17 -0
  106. data/ext/couchbase/couchbase/error_codes.hxx +1 -0
  107. data/ext/couchbase/couchbase/logger.hxx +16 -0
  108. data/ext/couchbase/couchbase/management/bucket_settings.hxx +1 -0
  109. data/ext/couchbase/couchbase/query_options.hxx +70 -6
  110. data/ext/couchbase/couchbase/transactions/async_attempt_context.hxx +29 -5
  111. data/ext/couchbase/couchbase/transactions/attempt_context.hxx +24 -7
  112. data/ext/couchbase/couchbase/transactions/transaction_get_multi_mode.hxx +47 -0
  113. data/ext/couchbase/couchbase/transactions/transaction_get_multi_options.hxx +44 -0
  114. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_mode.hxx +46 -0
  115. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_options.hxx +48 -0
  116. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +109 -0
  117. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_spec.hxx +47 -0
  118. data/ext/couchbase/couchbase/transactions/transaction_get_multi_result.hxx +102 -0
  119. data/ext/couchbase/couchbase/transactions/transaction_get_multi_spec.hxx +45 -0
  120. data/ext/extconf.rb +6 -0
  121. data/ext/rcb_buckets.cxx +26 -0
  122. data/lib/active_support/cache/couchbase_store.rb +1 -1
  123. data/lib/couchbase/cluster.rb +1 -1
  124. data/lib/couchbase/collection.rb +1 -1
  125. data/lib/couchbase/collection_options.rb +2 -2
  126. data/lib/couchbase/management/analytics_index_manager.rb +4 -4
  127. data/lib/couchbase/management/bucket_manager.rb +8 -2
  128. data/lib/couchbase/protostellar/cluster.rb +2 -2
  129. data/lib/couchbase/protostellar/collection.rb +1 -1
  130. data/lib/couchbase/protostellar/management/collection_query_index_manager.rb +1 -1
  131. data/lib/couchbase/protostellar/request_generator/admin/bucket.rb +4 -4
  132. data/lib/couchbase/protostellar/request_generator/admin/collection.rb +6 -6
  133. data/lib/couchbase/protostellar/request_generator/admin/query.rb +13 -13
  134. data/lib/couchbase/protostellar/request_generator/kv.rb +25 -25
  135. data/lib/couchbase/protostellar/request_generator/query.rb +4 -4
  136. data/lib/couchbase/protostellar/request_generator/search.rb +25 -25
  137. data/lib/couchbase/protostellar/response_converter/search.rb +1 -1
  138. data/lib/couchbase/protostellar/retry/reason.rb +1 -1
  139. data/lib/couchbase/protostellar/timeouts.rb +1 -1
  140. data/lib/couchbase/scope.rb +1 -1
  141. data/lib/couchbase/transcoder_flags.rb +1 -1
  142. data/lib/couchbase/utils/stdlib_logger_adapter.rb +1 -1
  143. data/lib/couchbase/version.rb +1 -1
  144. metadata +47 -19
  145. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/COPYING +0 -0
  146. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/cmake/SnappyConfig.cmake.in +0 -0
  147. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/cmake/config.h.in +0 -0
  148. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-c.cc +0 -0
  149. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-c.h +0 -0
  150. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-internal.h +0 -0
  151. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-sinksource.cc +0 -0
  152. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-sinksource.h +0 -0
  153. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-stubs-internal.cc +0 -0
  154. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-stubs-internal.h +0 -0
  155. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-stubs-public.h.in +0 -0
  156. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy.h +0 -0
@@ -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
 
@@ -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.ctx()), {});
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.ctx()), {});
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
  });
@@ -1809,8 +2151,8 @@ attempt_context_impl::do_public_query(
1809
2151
  }
1810
2152
 
1811
2153
  auto
1812
- make_params(const core::document_id& id,
1813
- std::optional<codec::encoded_value> content) -> std::vector<core::json_string>
2154
+ make_params(const core::document_id& id, std::optional<codec::encoded_value> content)
2155
+ -> std::vector<core::json_string>
1814
2156
  {
1815
2157
  if (content && !codec::codec_flags::has_common_flags(content->flags,
1816
2158
  codec::codec_flags::json_common_flags)) {
@@ -2939,20 +3281,32 @@ attempt_context_impl::do_get(const core::document_id& id,
2939
3281
  {
2940
3282
  try {
2941
3283
  if (check_expiry_pre_commit(STAGE_GET, id.key())) {
2942
- return cb(FAIL_EXPIRY, "expired in do_get", std::nullopt);
3284
+ return cb(FAIL_EXPIRY, std::nullopt, "expired in do_get", std::nullopt);
2943
3285
  }
2944
3286
 
3287
+ // Check if we already have a staged insert/replace for this document AND we have the content
3288
+ // for it (i.e. the cluster does not support replace body_with_xattr)
2945
3289
  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()));
3290
+ const auto own_write_content = own_write->staged_content();
3291
+ if (own_write_content.has_value()) {
3292
+ CB_ATTEMPT_CTX_LOG_DEBUG(this, "found own-write of mutated doc {}", id);
3293
+ return cb(std::nullopt,
3294
+ std::nullopt,
3295
+ std::nullopt,
3296
+ transaction_get_result{
3297
+ own_write->id(),
3298
+ codec::encoded_value{ own_write_content.value(), own_write->staged_flags() },
3299
+ own_write->cas().value(),
3300
+ {},
3301
+ {},
3302
+ });
3303
+ }
2950
3304
  }
2951
3305
  if (const staged_mutation* own_remove = staged_mutations_->find_remove(id);
2952
3306
  own_remove != nullptr) {
2953
3307
  auto msg = fmt::format("found own-write of removed doc {}", id);
2954
3308
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "{}", msg);
2955
- return cb(FAIL_DOC_NOT_FOUND, msg, std::nullopt);
3309
+ return cb(FAIL_DOC_NOT_FOUND, std::nullopt, msg, std::nullopt);
2956
3310
  }
2957
3311
 
2958
3312
  return hooks_.before_doc_get(
@@ -2964,7 +3318,7 @@ attempt_context_impl::do_get(const core::document_id& id,
2964
3318
  resolving_missing_atr_entry = std::move(resolving_missing_atr_entry),
2965
3319
  cb = std::forward<Handler>(cb)](auto ec) mutable {
2966
3320
  if (ec) {
2967
- return cb(ec, "before_doc_get hook raised error", std::nullopt);
3321
+ return cb(ec, std::nullopt, "before_doc_get hook raised error", std::nullopt);
2968
3322
  }
2969
3323
 
2970
3324
  return self->get_doc(
@@ -2975,115 +3329,133 @@ attempt_context_impl::do_get(const core::document_id& id,
2975
3329
  allow_replica,
2976
3330
  resolving_missing_atr_entry = std::move(resolving_missing_atr_entry),
2977
3331
  cb = std::move(cb)](std::optional<error_class> ec,
3332
+ std::optional<external_exception> cause,
2978
3333
  const std::optional<std::string>& err_message,
2979
3334
  std::optional<transaction_get_result> doc) mutable {
2980
3335
  if (!ec && !doc) {
2981
3336
  // it just isn't there.
2982
- return cb(std::nullopt, std::nullopt, std::nullopt);
3337
+ return cb(std::nullopt, std::nullopt, std::nullopt, std::nullopt);
2983
3338
  }
2984
- if (!ec) {
2985
- if (doc->links().is_document_in_transaction()) {
3339
+
3340
+ if (ec) {
3341
+ return cb(ec, cause, err_message, std::nullopt);
3342
+ }
3343
+
3344
+ if (!doc->links().is_document_in_transaction()) {
3345
+ if (doc->links().is_deleted()) {
2986
3346
  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
- }
3347
+ "doc not in txn, and is_deleted, so not returning it.");
3348
+ // doc has been deleted, not in txn, so don't return it
3349
+ return cb(std::nullopt, std::nullopt, std::nullopt, std::nullopt);
3350
+ }
3351
+ return cb(std::nullopt, std::nullopt, std::nullopt, doc);
3352
+ }
3000
3353
 
3001
- return cb(std::nullopt, std::nullopt, doc);
3002
- }
3354
+ if (doc->links().staged_attempt_id() == self->id()) {
3355
+ // This is a RYOW, and we can optimise here by not looking up the document's ATR.
3003
3356
 
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
- }
3357
+ if (doc->links().is_document_being_removed()) {
3358
+ // The document is being removed by this attempt, return empty.
3359
+ return cb(std::nullopt, std::nullopt, std::nullopt, std::nullopt);
3360
+ }
3361
+
3362
+ // Return the post-transaction version.
3363
+ return cb(std::nullopt,
3364
+ std::nullopt,
3365
+ std::nullopt,
3366
+ transaction_get_result::create_from(
3367
+ *doc, doc->links().staged_content_json_or_binary()));
3368
+ }
3369
+
3370
+ CB_ATTEMPT_CTX_LOG_DEBUG(self,
3371
+ "doc {} in transaction, resolving_missing_atr_entry={}",
3372
+ *doc,
3373
+ resolving_missing_atr_entry.value_or("-"));
3374
+
3375
+ if (resolving_missing_atr_entry.has_value() &&
3376
+ resolving_missing_atr_entry.value() == doc->links().staged_attempt_id()) {
3377
+ CB_ATTEMPT_CTX_LOG_DEBUG(self, "doc is in lost pending transaction");
3378
+
3379
+ if (doc->links().is_document_being_inserted()) {
3380
+ // this document is being inserted, so should not be visible
3381
+ // yet
3382
+ return cb(std::nullopt, std::nullopt, std::nullopt, std::nullopt);
3383
+ }
3384
+
3385
+ return cb(std::nullopt, std::nullopt, std::nullopt, doc);
3386
+ }
3387
+
3388
+ const core::document_id doc_atr_id{ doc->links().atr_bucket_name().value(),
3389
+ doc->links().atr_scope_name().value(),
3390
+ doc->links().atr_collection_name().value(),
3391
+ doc->links().atr_id().value() };
3392
+ active_transaction_record::get_atr(
3393
+ self->cluster_ref(),
3394
+ doc_atr_id,
3395
+ [self, id, allow_replica, doc, cb = std::move(cb)](
3396
+ std::error_code ec2, std::optional<active_transaction_record> atr) mutable {
3397
+ if (!ec2 && atr) {
3398
+ const active_transaction_record& atr_doc = atr.value();
3399
+ std::optional<atr_entry> entry;
3400
+ for (const auto& e : atr_doc.entries()) {
3401
+ if (doc->links().staged_attempt_id().value() == e.attempt_id()) {
3402
+ entry.emplace(e);
3403
+ break;
3404
+ }
3405
+ }
3406
+ bool ignore_doc = false;
3407
+ auto content = doc->content();
3408
+ if (entry) {
3409
+ if (doc->links().staged_attempt_id() && entry->attempt_id() == self->id()) {
3410
+ // Attempt is reading its own writes
3411
+ // This is here as backup, it should be returned
3412
+ // from the in-memory cache instead
3413
+ content = doc->links().staged_content_json_or_binary();
3414
+ } else {
3415
+ auto err = check_forward_compat(forward_compat_stage::GETS_READING_ATR,
3416
+ entry->forward_compat());
3417
+ if (err) {
3418
+ return cb(FAIL_OTHER, err->cause(), err->what(), std::nullopt);
3021
3419
  }
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);
3420
+ switch (entry->state()) {
3421
+ case attempt_state::COMPLETED:
3422
+ case attempt_state::COMMITTED:
3423
+ if (doc->links().is_document_being_removed()) {
3424
+ ignore_doc = true;
3425
+ } else {
3426
+ content = doc->links().staged_content_json_or_binary();
3035
3427
  }
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;
3428
+ break;
3429
+ default:
3430
+ if (doc->links().is_document_being_inserted()) {
3431
+ // This document is being inserted, so should
3432
+ // not be visible yet
3433
+ ignore_doc = true;
3052
3434
  }
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);
3435
+ break;
3064
3436
  }
3065
- return cb(std::nullopt,
3066
- std::nullopt,
3067
- transaction_get_result::create_from(*doc, content));
3068
3437
  }
3069
- // failed to get the ATR
3438
+ } else {
3439
+ // failed to get the ATR entry
3070
3440
  CB_ATTEMPT_CTX_LOG_DEBUG(self,
3071
- "could not get ATR, checking again with {}",
3441
+ "could not get ATR entry, checking again with {}",
3072
3442
  doc->links().staged_attempt_id().value_or("-"));
3073
3443
  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);
3444
+ }
3445
+ if (ignore_doc) {
3446
+ return cb(std::nullopt, std::nullopt, std::nullopt, std::nullopt);
3447
+ }
3448
+ return cb(std::nullopt,
3449
+ std::nullopt,
3450
+ std::nullopt,
3451
+ transaction_get_result::create_from(*doc, content));
3081
3452
  }
3082
- return cb(std::nullopt, std::nullopt, doc);
3083
- }
3084
- } else {
3085
- return cb(ec, err_message, std::nullopt);
3086
- }
3453
+ // failed to get the ATR
3454
+ CB_ATTEMPT_CTX_LOG_DEBUG(self,
3455
+ "could not get ATR, checking again with {}",
3456
+ doc->links().staged_attempt_id().value_or("-"));
3457
+ return self->do_get(id, allow_replica, doc->links().staged_attempt_id(), cb);
3458
+ });
3087
3459
  });
3088
3460
  });
3089
3461
  } catch (const transaction_operation_failed&) {
@@ -3108,12 +3480,17 @@ execute_lookup(attempt_context_impl* ctx, LookupInRequest& req, Callback&& cb)
3108
3480
  CB_ATTEMPT_CTX_LOG_TRACE(ctx, "get_doc got error {} : {}", resp.ctx.ec().message(), *ec);
3109
3481
  switch (*ec) {
3110
3482
  case FAIL_PATH_NOT_FOUND:
3111
- return cb(ec, resp.ctx.ec().message(), transaction_get_result::create_from(resp));
3483
+ return cb(ec,
3484
+ external_exception_from_response(resp),
3485
+ resp.ctx.ec().message(),
3486
+ transaction_get_result::create_from(resp));
3112
3487
  default:
3113
- return cb(ec, resp.ctx.ec().message(), std::nullopt);
3488
+ return cb(
3489
+ ec, external_exception_from_response(resp), resp.ctx.ec().message(), std::nullopt);
3114
3490
  }
3115
3491
  } else {
3116
- return cb({}, {}, transaction_get_result::create_from(resp));
3492
+ return cb(
3493
+ std::nullopt, std::nullopt, std::nullopt, transaction_get_result::create_from(resp));
3117
3494
  }
3118
3495
  });
3119
3496
  }
@@ -3123,6 +3500,7 @@ void
3123
3500
  attempt_context_impl::get_doc(const core::document_id& id,
3124
3501
  bool allow_replica,
3125
3502
  std::function<void(std::optional<error_class>,
3503
+ std::optional<external_exception>,
3126
3504
  std::optional<std::string>,
3127
3505
  std::optional<transaction_get_result>)>&& cb)
3128
3506
  {
@@ -3145,7 +3523,7 @@ attempt_context_impl::get_doc(const core::document_id& id,
3145
3523
  try {
3146
3524
  if (allow_replica) {
3147
3525
  core::operations::lookup_in_any_replica_request req{ id };
3148
- req.read_preference = couchbase::read_preference::selected_server_group;
3526
+ req.read_preference = couchbase::read_preference::selected_server_group_or_all_available;
3149
3527
  req.specs = specs;
3150
3528
  execute_lookup(this, req, cb);
3151
3529
  } else {
@@ -3155,7 +3533,7 @@ attempt_context_impl::get_doc(const core::document_id& id,
3155
3533
  execute_lookup(this, req, cb);
3156
3534
  }
3157
3535
  } catch (const std::exception& e) {
3158
- return cb(FAIL_OTHER, e.what(), std::nullopt);
3536
+ return cb(FAIL_OTHER, std::nullopt, e.what(), std::nullopt);
3159
3537
  }
3160
3538
  }
3161
3539
 
@@ -3252,6 +3630,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
3252
3630
  false,
3253
3631
  [self, id, content, op_id, cb = std::forward<Handler>(cb), error_handler, delay](
3254
3632
  std::optional<error_class> ec3,
3633
+ std::optional<external_exception> /* cause */,
3255
3634
  std::optional<std::string> err_message,
3256
3635
  std::optional<transaction_get_result> doc) mutable {
3257
3636
  if (!ec3) {
@@ -3263,8 +3642,9 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
3263
3642
  doc->links().is_document_in_transaction(),
3264
3643
  doc->links().is_deleted());
3265
3644
 
3266
- if (auto err = check_forward_compat(forward_compat_stage::WWC_INSERTING_GET,
3267
- doc->links().forward_compat());
3645
+ if (auto err = check_forward_compat(
3646
+ forward_compat_stage::WRITE_WRITE_CONFLICT_INSERTING_GET,
3647
+ doc->links().forward_compat());
3268
3648
  err) {
3269
3649
  return self->op_completed_with_error(std::forward<Handler>(cb), *err);
3270
3650
  }
@@ -3293,8 +3673,15 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
3293
3673
  // this is us dealing with resolving an ambiguity. So, lets
3294
3674
  // just update the staged_mutation with the correct cas and
3295
3675
  // continue...
3296
- self->staged_mutations_->add(
3297
- staged_mutation(*doc, content, staged_mutation_type::INSERT));
3676
+ self->staged_mutations_->add(staged_mutation{
3677
+ staged_mutation_type::INSERT,
3678
+ doc->id(),
3679
+ doc->cas(),
3680
+ content.data,
3681
+ content.flags,
3682
+ doc->content().flags,
3683
+ doc->metadata(),
3684
+ });
3298
3685
  return self->op_completed_with_callback(std::forward<Handler>(cb), doc);
3299
3686
  }
3300
3687
  return self->op_completed_with_error(
@@ -3313,7 +3700,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
3313
3700
  }
3314
3701
  self->check_and_handle_blocking_transactions(
3315
3702
  *doc,
3316
- forward_compat_stage::WWC_INSERTING,
3703
+ forward_compat_stage::WRITE_WRITE_CONFLICT_INSERTING,
3317
3704
  [self, id, op_id, content, doc, cb = std::forward<Handler>(cb), delay](
3318
3705
  std::optional<transaction_operation_failed> err) mutable {
3319
3706
  if (err) {
@@ -3459,55 +3846,89 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
3459
3846
 
3460
3847
  CB_ATTEMPT_CTX_LOG_DEBUG(
3461
3848
  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) });
3849
+
3850
+ self->supports_replace_body_with_xattr(
3851
+ id.bucket(),
3852
+ [self,
3853
+ id,
3854
+ content = std::move(content),
3855
+ cas,
3856
+ op_id,
3857
+ delay = std::forward<Delay>(delay),
3858
+ resp = std::move(resp),
3859
+ cb = std::forward<Handler>(cb)](auto ec, bool supports) mutable {
3860
+ if (ec) {
3861
+ return self->create_staged_insert_error_handler(
3862
+ id,
3863
+ std::move(content),
3864
+ cas,
3865
+ std::forward<Delay>(delay),
3866
+ op_id,
3867
+ std::forward<Handler>(cb),
3868
+ FAIL_OTHER,
3869
+ UNKNOWN,
3870
+ "failed to check whether replace_body_with_xattr is supported: " + ec.message());
3871
+ }
3872
+
3873
+ std::optional<codec::encoded_value> staged_content_json{};
3874
+ std::optional<codec::encoded_value> staged_content_binary{};
3875
+ if (codec::codec_flags::has_common_flags(content.flags,
3876
+ codec::codec_flags::json_common_flags)) {
3877
+ staged_content_json = content;
3878
+ } else if (codec::codec_flags::has_common_flags(
3879
+ content.flags, codec::codec_flags::binary_common_flags)) {
3880
+ staged_content_binary = content;
3881
+ }
3882
+
3883
+ transaction_get_result out{
3884
+ id,
3885
+ content,
3886
+ resp.cas.value(),
3887
+ transaction_links{
3888
+ self->atr_id_->key(),
3889
+ id.bucket(),
3890
+ id.scope(),
3891
+ id.collection(),
3892
+ self->overall()->transaction_id(),
3893
+ self->id(),
3894
+ op_id,
3895
+ std::move(staged_content_json),
3896
+ std::move(staged_content_binary),
3897
+ std::nullopt,
3898
+ std::nullopt,
3899
+ std::nullopt,
3900
+ std::nullopt,
3901
+ "insert",
3902
+ std::nullopt,
3903
+ true,
3904
+ },
3905
+ std::nullopt,
3906
+ };
3907
+
3908
+ auto [staged_content, staged_flags] = out.links().staged_content_json_or_binary();
3909
+
3910
+ self->staged_mutations_->add(staged_mutation{
3911
+ staged_mutation_type::INSERT,
3912
+ id,
3913
+ resp.cas,
3914
+ supports ? std::nullopt
3915
+ : std::make_optional(staged_content), // We don't store the staged contents
3916
+ // if the cluster supports
3917
+ // replace_body_with_xattr
3918
+ staged_flags,
3919
+ staged_flags,
3920
+ out.metadata(),
3921
+ });
3922
+ return self->op_completed_with_callback(std::forward<Handler>(cb),
3923
+ std::optional{ std::move(out) });
3924
+ });
3504
3925
  });
3505
3926
  });
3506
3927
  }
3507
3928
 
3508
3929
  void
3509
3930
  attempt_context_impl::ensure_open_bucket(const std::string& bucket_name,
3510
- std::function<void(std::error_code)>&& handler)
3931
+ std::function<void(std::error_code)>&& handler) const
3511
3932
  {
3512
3933
  if (bucket_name.empty()) {
3513
3934
  CB_LOG_DEBUG("ensure_open_bucket called with empty bucket_name");
@@ -3518,6 +3939,29 @@ attempt_context_impl::ensure_open_bucket(const std::string& bucket_name,
3518
3939
  });
3519
3940
  }
3520
3941
 
3942
+ void
3943
+ attempt_context_impl::supports_replace_body_with_xattr(
3944
+ const std::string& bucket_name,
3945
+ std::function<void(std::error_code, bool)>&& handler) const
3946
+ {
3947
+ cluster_ref().with_bucket_configuration(
3948
+ bucket_name,
3949
+ [handler = std::move(handler)](std::error_code ec,
3950
+ const std::shared_ptr<topology::configuration>& config) {
3951
+ if (ec) {
3952
+ handler(ec, {});
3953
+ return;
3954
+ }
3955
+
3956
+ // We use subdoc_revive_document instead of subdoc_replace_body_with_xattr.
3957
+ // The version where subdoc_replace_body_with_xattr was released (7.0) contained a significant
3958
+ // bug.
3959
+ // (https://github.com/couchbaselabs/couchbase-transactions-specs/blob/master/transactions-stages.md#supportsreplacebodywithxattr)
3960
+ handler(
3961
+ {}, config->capabilities.has_bucket_capability(bucket_capability::subdoc_revive_document));
3962
+ });
3963
+ }
3964
+
3521
3965
  void
3522
3966
  attempt_context_impl::remove(couchbase::transactions::transaction_get_result doc,
3523
3967
  couchbase::transactions::async_err_handler&& handler)
@@ -3729,4 +4173,10 @@ attempt_context_impl::atr_collection_name(const std::string& coll) const
3729
4173
  {
3730
4174
  overall()->atr_collection(coll);
3731
4175
  }
4176
+
4177
+ auto
4178
+ attempt_context_impl::expiry_time() const -> std::chrono::steady_clock::time_point
4179
+ {
4180
+ return overall()->expiry_time();
4181
+ }
3732
4182
  } // namespace couchbase::core::transactions