couchbase 3.4.0 → 3.4.2

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/couchbase/CMakeLists.txt +10 -3
  4. data/ext/couchbase/cmake/CompilerWarnings.cmake +12 -4
  5. data/ext/couchbase/cmake/Documentation.cmake +4 -3
  6. data/ext/couchbase/cmake/OpenSSL.cmake +52 -7
  7. data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +4 -0
  8. data/ext/couchbase/cmake/VersionInfo.cmake +39 -3
  9. data/ext/couchbase/cmake/test_openssl.cxx +7 -0
  10. data/ext/couchbase/core/cluster_options.hxx +0 -1
  11. data/ext/couchbase/core/config_profile.cxx +23 -1
  12. data/ext/couchbase/core/config_profile.hxx +2 -12
  13. data/ext/couchbase/core/crypto/CMakeLists.txt +5 -1
  14. data/ext/couchbase/core/impl/analytics.cxx +236 -0
  15. data/ext/couchbase/core/impl/cluster.cxx +0 -1
  16. data/ext/couchbase/core/impl/collection_query_index_manager.cxx +3 -3
  17. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +5 -3
  18. data/ext/couchbase/core/impl/get_all_query_indexes.cxx +3 -3
  19. data/ext/couchbase/core/impl/query.cxx +5 -5
  20. data/ext/couchbase/core/impl/transaction_get_result.cxx +54 -0
  21. data/ext/couchbase/core/io/dns_client.cxx +225 -0
  22. data/ext/couchbase/core/io/dns_client.hxx +19 -188
  23. data/ext/couchbase/core/meta/CMakeLists.txt +7 -5
  24. data/ext/couchbase/core/meta/version.cxx +19 -0
  25. data/ext/couchbase/core/operations/document_search.cxx +5 -2
  26. data/ext/couchbase/core/operations/document_search.hxx +0 -1
  27. data/ext/couchbase/core/transactions/active_transaction_record.hxx +2 -2
  28. data/ext/couchbase/core/transactions/atr_cleanup_entry.cxx +1 -0
  29. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +65 -31
  30. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +44 -23
  31. data/ext/couchbase/core/transactions/forward_compat.hxx +2 -2
  32. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +13 -13
  33. data/ext/couchbase/core/transactions/internal/transaction_fields.hxx +1 -0
  34. data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +7 -1
  35. data/ext/couchbase/core/transactions/staged_mutation.cxx +1 -1
  36. data/ext/couchbase/core/transactions/staged_mutation.hxx +12 -2
  37. data/ext/couchbase/core/transactions/transaction_context.cxx +9 -11
  38. data/ext/couchbase/core/transactions/transaction_get_result.cxx +41 -31
  39. data/ext/couchbase/core/transactions/transaction_get_result.hxx +7 -3
  40. data/ext/couchbase/core/transactions/transaction_links.hxx +13 -1
  41. data/ext/couchbase/core/transactions/transactions_cleanup.cxx +144 -155
  42. data/ext/couchbase/core/transactions/waitable_op_list.hxx +1 -0
  43. data/ext/couchbase/core/utils/connection_string.cxx +10 -3
  44. data/ext/couchbase/core/utils/connection_string.hxx +3 -3
  45. data/ext/couchbase/couchbase/analytics_error_context.hxx +143 -0
  46. data/ext/couchbase/couchbase/analytics_meta_data.hxx +155 -0
  47. data/ext/couchbase/couchbase/analytics_metrics.hxx +163 -0
  48. data/ext/couchbase/couchbase/analytics_options.hxx +359 -0
  49. data/ext/couchbase/couchbase/analytics_result.hxx +102 -0
  50. data/ext/couchbase/couchbase/analytics_scan_consistency.hxx +46 -0
  51. data/ext/couchbase/couchbase/analytics_status.hxx +41 -0
  52. data/ext/couchbase/couchbase/analytics_warning.hxx +85 -0
  53. data/ext/couchbase/couchbase/cluster.hxx +35 -2
  54. data/ext/couchbase/couchbase/cluster_options.hxx +10 -10
  55. data/ext/couchbase/couchbase/collection.hxx +22 -17
  56. data/ext/couchbase/couchbase/collection_query_index_manager.hxx +1 -1
  57. data/ext/couchbase/couchbase/common_options.hxx +1 -1
  58. data/ext/couchbase/couchbase/configuration_profile.hxx +1 -1
  59. data/ext/couchbase/couchbase/configuration_profiles_registry.hxx +0 -1
  60. data/ext/couchbase/couchbase/create_primary_query_index_options.hxx +1 -1
  61. data/ext/couchbase/couchbase/drop_primary_query_index_options.hxx +1 -1
  62. data/ext/couchbase/couchbase/drop_query_index_options.hxx +1 -1
  63. data/ext/couchbase/couchbase/fmt/analytics_status.hxx +76 -0
  64. data/ext/couchbase/couchbase/fmt/cas.hxx +12 -0
  65. data/ext/couchbase/couchbase/fmt/durability_level.hxx +6 -0
  66. data/ext/couchbase/couchbase/fmt/key_value_extended_error_info.hxx +6 -0
  67. data/ext/couchbase/couchbase/fmt/key_value_status_code.hxx +6 -0
  68. data/ext/couchbase/couchbase/fmt/mutation_token.hxx +6 -0
  69. data/ext/couchbase/couchbase/fmt/query_scan_consistency.hxx +6 -0
  70. data/ext/couchbase/couchbase/fmt/query_status.hxx +6 -0
  71. data/ext/couchbase/couchbase/fmt/retry_reason.hxx +6 -0
  72. data/ext/couchbase/couchbase/fmt/tls_verify_mode.hxx +6 -0
  73. data/ext/couchbase/couchbase/get_all_query_indexes_options.hxx +5 -4
  74. data/ext/couchbase/couchbase/query_index_manager.hxx +4 -2
  75. data/ext/couchbase/couchbase/query_options.hxx +0 -1
  76. data/ext/couchbase/couchbase/scope.hxx +34 -1
  77. data/ext/couchbase/couchbase/subdoc/array_add_unique.hxx +2 -0
  78. data/ext/couchbase/couchbase/subdoc/array_append.hxx +2 -0
  79. data/ext/couchbase/couchbase/subdoc/array_insert.hxx +2 -0
  80. data/ext/couchbase/couchbase/subdoc/array_prepend.hxx +2 -0
  81. data/ext/couchbase/couchbase/subdoc/count.hxx +2 -0
  82. data/ext/couchbase/couchbase/subdoc/counter.hxx +2 -0
  83. data/ext/couchbase/couchbase/subdoc/exists.hxx +2 -0
  84. data/ext/couchbase/couchbase/subdoc/get.hxx +2 -0
  85. data/ext/couchbase/couchbase/subdoc/insert.hxx +2 -0
  86. data/ext/couchbase/couchbase/subdoc/remove.hxx +2 -0
  87. data/ext/couchbase/couchbase/subdoc/replace.hxx +3 -1
  88. data/ext/couchbase/couchbase/subdoc/upsert.hxx +2 -0
  89. data/ext/couchbase/couchbase/transaction_op_error_context.hxx +4 -4
  90. data/ext/couchbase/couchbase/transactions/attempt_context.hxx +1 -1
  91. data/ext/couchbase/couchbase/transactions/transaction_get_result.hxx +36 -51
  92. data/ext/couchbase/couchbase/transactions/transactions_config.hxx +1 -1
  93. data/ext/couchbase/test/CMakeLists.txt +3 -2
  94. data/ext/couchbase/test/test_helper.hxx +1 -1
  95. data/ext/couchbase/test/test_integration_analytics.cxx +289 -13
  96. data/ext/couchbase/test/test_integration_crud.cxx +8 -1
  97. data/ext/couchbase/test/test_integration_examples.cxx +182 -0
  98. data/ext/couchbase/test/test_integration_management.cxx +15 -3
  99. data/ext/couchbase/test/test_integration_search.cxx +601 -0
  100. data/ext/couchbase/test/test_transaction_transaction_simple.cxx +73 -0
  101. data/ext/couchbase/test/test_unit_config_profiles.cxx +12 -12
  102. data/ext/couchbase/test/test_unit_connection_string.cxx +35 -0
  103. data/ext/couchbase/test/test_unit_transaction_utils.cxx +76 -19
  104. data/ext/couchbase/third_party/snappy/CMakeLists.txt +150 -27
  105. data/ext/couchbase/third_party/snappy/cmake/config.h.in +28 -24
  106. data/ext/couchbase/third_party/snappy/snappy-internal.h +189 -25
  107. data/ext/couchbase/third_party/snappy/snappy-sinksource.cc +26 -9
  108. data/ext/couchbase/third_party/snappy/snappy-sinksource.h +11 -11
  109. data/ext/couchbase/third_party/snappy/snappy-stubs-internal.cc +1 -1
  110. data/ext/couchbase/third_party/snappy/snappy-stubs-internal.h +227 -308
  111. data/ext/couchbase/third_party/snappy/snappy-stubs-public.h.in +0 -11
  112. data/ext/couchbase/third_party/snappy/snappy.cc +1176 -410
  113. data/ext/couchbase/third_party/snappy/snappy.h +19 -4
  114. data/ext/couchbase.cxx +506 -26
  115. data/ext/extconf.rb +2 -1
  116. data/ext/revisions.rb +3 -2
  117. data/lib/couchbase/binary_collection.rb +4 -4
  118. data/lib/couchbase/cluster.rb +13 -9
  119. data/lib/couchbase/cluster_registry.rb +7 -2
  120. data/lib/couchbase/collection.rb +5 -0
  121. data/lib/couchbase/configuration.rb +3 -4
  122. data/lib/couchbase/errors.rb +10 -0
  123. data/lib/couchbase/management/collection_query_index_manager.rb +183 -0
  124. data/lib/couchbase/management/query_index_manager.rb +35 -3
  125. data/lib/couchbase/management.rb +1 -0
  126. data/lib/couchbase/options.rb +87 -5
  127. data/lib/couchbase/search_options.rb +158 -240
  128. data/lib/couchbase/version.rb +1 -1
  129. metadata +21 -6
  130. data/ext/couchbase/core/CMakeLists.txt +0 -0
@@ -174,6 +174,7 @@ atr_cleanup_entry::do_per_doc(std::vector<doc_record> docs,
174
174
  lookup_in_specs::get(ATR_ID).xattr(),
175
175
  lookup_in_specs::get(TRANSACTION_ID).xattr(),
176
176
  lookup_in_specs::get(ATTEMPT_ID).xattr(),
177
+ lookup_in_specs::get(OPERATION_ID).xattr(),
177
178
  lookup_in_specs::get(STAGED_DATA).xattr(),
178
179
  lookup_in_specs::get(ATR_BUCKET_NAME).xattr(),
179
180
  lookup_in_specs::get(ATR_SCOPE_NAME).xattr(),
@@ -243,6 +243,7 @@ core::operations::mutate_in_request
243
243
  attempt_context_impl::create_staging_request(const core::document_id& id,
244
244
  const transaction_get_result* document,
245
245
  const std::string type,
246
+ const std::string op_id,
246
247
  std::optional<std::vector<std::byte>> content)
247
248
  {
248
249
  core::operations::mutate_in_request req{ id };
@@ -250,6 +251,7 @@ attempt_context_impl::create_staging_request(const core::document_id& id,
250
251
  txn["id"] = tao::json::empty_object;
251
252
  txn["id"]["txn"] = transaction_id();
252
253
  txn["id"]["atmpt"] = this->id();
254
+ txn["id"]["op"] = op_id;
253
255
  txn["atr"] = tao::json::empty_object;
254
256
  txn["atr"]["id"] = atr_id();
255
257
  txn["atr"]["bkt"] = atr_id_->bucket();
@@ -296,6 +298,7 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
296
298
  return op_completed_with_error(std::move(cb), transaction_operation_failed(FAIL_OTHER, ec.message()));
297
299
  }
298
300
  try {
301
+ auto op_id = uid_generator::next();
299
302
  // a get can return a 'empty' doc, so check for that and short-circuit the eventual error that will occur...
300
303
  if (document.key().empty() || document.bucket().empty()) {
301
304
  return op_completed_with_error(std::move(cb),
@@ -321,7 +324,7 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
321
324
  check_and_handle_blocking_transactions(
322
325
  document,
323
326
  forward_compat_stage::WWC_REPLACING,
324
- [this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), content](
327
+ [this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), op_id, content](
325
328
  std::optional<transaction_operation_failed> e1) mutable {
326
329
  if (e1) {
327
330
  return op_completed_with_error(std::move(cb), *e1);
@@ -330,7 +333,7 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
330
333
  document_id{ document.id().bucket(), document.id().scope(), document.id().collection(), document.id().key() };
331
334
  select_atr_if_needed_unlocked(
332
335
  tmp_doc,
333
- [this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), content](
336
+ [this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), op_id, content](
334
337
  std::optional<transaction_operation_failed> e2) mutable {
335
338
  if (e2) {
336
339
  return op_completed_with_error(std::move(cb), *e2);
@@ -339,10 +342,10 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
339
342
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing INSERT of {} while replacing", document);
340
343
  exp_delay delay(
341
344
  std::chrono::milliseconds(5), std::chrono::milliseconds(300), overall_.config().expiration_time);
342
- create_staged_insert(document.id(), content, existing_sm->doc().cas().value(), delay, std::move(cb));
345
+ create_staged_insert(document.id(), content, existing_sm->doc().cas().value(), delay, op_id, std::move(cb));
343
346
  return;
344
347
  }
345
- create_staged_replace(document, content, std::move(cb));
348
+ create_staged_replace(document, content, op_id, std::move(cb));
346
349
  });
347
350
  });
348
351
  } catch (const client_error& e) {
@@ -361,9 +364,12 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
361
364
 
362
365
  template<typename Handler>
363
366
  void
364
- attempt_context_impl::create_staged_replace(const transaction_get_result& document, const std::vector<std::byte>& content, Handler&& cb)
367
+ attempt_context_impl::create_staged_replace(const transaction_get_result& document,
368
+ const std::vector<std::byte>& content,
369
+ const std::string& op_id,
370
+ Handler&& cb)
365
371
  {
366
- auto req = create_staging_request(document.id(), &document, "replace", content);
372
+ auto req = create_staging_request(document.id(), &document, "replace", op_id, content);
367
373
  req.cas = document.cas();
368
374
  req.access_deleted = true;
369
375
  auto error_handler = [this](error_class ec, const std::string& msg, Handler&& cb) {
@@ -449,6 +455,7 @@ attempt_context_impl::insert_raw(const core::document_id& id, const std::vector<
449
455
  }
450
456
  try {
451
457
  check_if_done(cb);
458
+ auto op_id = uid_generator::next();
452
459
  staged_mutation* existing_sm = staged_mutations_->find_any(id);
453
460
  if ((existing_sm != nullptr) &&
454
461
  (existing_sm->type() == staged_mutation_type::INSERT || existing_sm->type() == staged_mutation_type::REPLACE)) {
@@ -462,17 +469,17 @@ attempt_context_impl::insert_raw(const core::document_id& id, const std::vector<
462
469
  transaction_operation_failed(FAIL_EXPIRY, "transaction expired").expired());
463
470
  }
464
471
  select_atr_if_needed_unlocked(
465
- id, [this, existing_sm, cb = std::move(cb), id, content](std::optional<transaction_operation_failed> err) mutable {
472
+ id, [this, existing_sm, cb = std::move(cb), id, op_id, content](std::optional<transaction_operation_failed> err) mutable {
466
473
  if (err) {
467
474
  return op_completed_with_error(std::move(cb), *err);
468
475
  }
469
476
  if (existing_sm != nullptr && existing_sm->type() == staged_mutation_type::REMOVE) {
470
477
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing remove of {} while inserting", id);
471
- return create_staged_replace(existing_sm->doc(), content, std::move(cb));
478
+ return create_staged_replace(existing_sm->doc(), content, op_id, std::move(cb));
472
479
  }
473
480
  uint64_t cas = 0;
474
481
  exp_delay delay(std::chrono::milliseconds(5), std::chrono::milliseconds(300), overall_.config().expiration_time);
475
- create_staged_insert(id, content, cas, delay, std::move(cb));
482
+ create_staged_insert(id, content, cas, delay, op_id, std::move(cb));
476
483
  });
477
484
  } catch (const std::exception& e) {
478
485
  return op_completed_with_error(std::move(cb), transaction_operation_failed(FAIL_OTHER, e.what()));
@@ -599,6 +606,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
599
606
  return error_handler(FAIL_EXPIRY, "transaction expired", std::move(cb));
600
607
  }
601
608
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "removing {}", document);
609
+ auto op_id = uid_generator::next();
602
610
  if (existing_sm != nullptr) {
603
611
  if (existing_sm->type() == staged_mutation_type::REMOVE) {
604
612
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing REMOVE of {} while removing", document);
@@ -616,7 +624,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
616
624
  check_and_handle_blocking_transactions(
617
625
  document,
618
626
  forward_compat_stage::WWC_REMOVING,
619
- [this, document = std::move(document), cb = std::move(cb), error_handler = std::move(error_handler)](
627
+ [this, document = std::move(document), cb = std::move(cb), op_id, error_handler = std::move(error_handler)](
620
628
  std::optional<transaction_operation_failed> err1) mutable {
621
629
  if (err1) {
622
630
  return op_completed_with_error(std::move(cb), *err1);
@@ -625,7 +633,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
625
633
  document_id{ document.id().bucket(), document.id().scope(), document.id().collection(), document.id().key() };
626
634
  select_atr_if_needed_unlocked(
627
635
  tmp_doc,
628
- [document = std::move(document), cb = std::move(cb), this, error_handler = std::move(error_handler)](
636
+ [document = std::move(document), cb = std::move(cb), this, op_id, error_handler = std::move(error_handler)](
629
637
  std::optional<transaction_operation_failed> err2) mutable {
630
638
  if (err2) {
631
639
  return op_completed_with_error(std::move(cb), *err2);
@@ -634,7 +642,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
634
642
  return error_handler(*ec, "before_staged_remove hook raised error", std::move(cb));
635
643
  }
636
644
  CB_ATTEMPT_CTX_LOG_TRACE(this, "about to remove doc {} with cas {}", document.id(), document.cas().value());
637
- auto req = create_staging_request(document.id(), &document, "remove");
645
+ auto req = create_staging_request(document.id(), &document, "remove", op_id);
638
646
  req.cas = document.cas();
639
647
  req.access_deleted = document.links().is_deleted();
640
648
  overall_.cluster_ref()->execute(
@@ -1139,6 +1147,9 @@ attempt_context_impl::get_with_query(const core::document_id& id, bool optional,
1139
1147
  // make a transaction_get_result from the row...
1140
1148
  try {
1141
1149
  if (resp.rows.empty()) {
1150
+ if (optional) {
1151
+ return op_completed_with_callback(std::move(cb), std::optional<transaction_get_result>());
1152
+ }
1142
1153
  return op_completed_with_error(
1143
1154
  std::move(cb), transaction_operation_failed(FAIL_DOC_NOT_FOUND, "document not found"));
1144
1155
  }
@@ -1905,10 +1916,10 @@ attempt_context_impl::set_atr_pending_locked(const core::document_id& id, std::u
1905
1916
  return fn(std::nullopt);
1906
1917
  case FAIL_AMBIGUOUS:
1907
1918
  // Retry just this
1908
- overall_.retry_delay();
1909
- // keep it locked!
1910
- CB_ATTEMPT_CTX_LOG_DEBUG(this, "got {}, retrying set atr pending", ec);
1911
- return set_atr_pending_locked(doc_id, std::move(lock), std::move(fn));
1919
+ CB_ATTEMPT_CTX_LOG_DEBUG(this, "got FAIL_AMBIGUOUS, retrying set atr pending", ec);
1920
+ return overall_.after_delay(std::chrono::milliseconds(1), [this, doc_id, &lock, fn = std::move(fn)]() {
1921
+ set_atr_pending_locked(doc_id, std::move(lock), std::move(fn));
1922
+ });
1912
1923
  case FAIL_TRANSIENT:
1913
1924
  // Retry txn
1914
1925
  return fn(err.retry());
@@ -1923,8 +1934,6 @@ attempt_context_impl::set_atr_pending_locked(const core::document_id& id, std::u
1923
1934
  }
1924
1935
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "updating atr {}", atr_id_.value());
1925
1936
 
1926
- // FIXME: do we need to capture "now" here?
1927
- // std::chrono::time_point<std::chrono::steady_clock> now = std::chrono::steady_clock::now();
1928
1937
  std::chrono::nanoseconds remaining = overall_.remaining();
1929
1938
  // This bounds the value to [0-expirationTime]. It should always be in this range, this is just to protect
1930
1939
  // against the application clock changing.
@@ -2153,6 +2162,7 @@ attempt_context_impl::get_doc(
2153
2162
  lookup_in_specs::get(ATR_ID).xattr(),
2154
2163
  lookup_in_specs::get(TRANSACTION_ID).xattr(),
2155
2164
  lookup_in_specs::get(ATTEMPT_ID).xattr(),
2165
+ lookup_in_specs::get(OPERATION_ID).xattr(),
2156
2166
  lookup_in_specs::get(STAGED_DATA).xattr(),
2157
2167
  lookup_in_specs::get(ATR_BUCKET_NAME).xattr(),
2158
2168
  lookup_in_specs::get(ATR_SCOPE_NAME).xattr(),
@@ -2193,6 +2203,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2193
2203
  const std::vector<std::byte>& content,
2194
2204
  uint64_t cas,
2195
2205
  Delay&& delay,
2206
+ const std::string& op_id,
2196
2207
  Handler&& cb,
2197
2208
  error_class ec,
2198
2209
  const std::string& message)
@@ -2211,7 +2222,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2211
2222
  case FAIL_AMBIGUOUS:
2212
2223
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "FAIL_AMBIGUOUS in create_staged_insert, retrying");
2213
2224
  delay();
2214
- return create_staged_insert(id, content, cas, delay, std::forward<Handler>(cb));
2225
+ return create_staged_insert(id, content, cas, delay, op_id, std::forward<Handler>(cb));
2215
2226
  case FAIL_OTHER:
2216
2227
  return op_completed_with_error(std::forward<Handler>(cb), transaction_operation_failed(ec, "error in create_staged_insert"));
2217
2228
  case FAIL_HARD:
@@ -2221,7 +2232,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2221
2232
  case FAIL_CAS_MISMATCH: {
2222
2233
  // special handling for doc already existing
2223
2234
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing doc {}, may still be able to insert", id);
2224
- auto error_handler = [this, id, content](error_class ec2, const std::string& err_message, Handler&& cb) mutable {
2235
+ auto error_handler = [this, id, op_id, content](error_class ec2, const std::string& err_message, Handler&& cb) mutable {
2225
2236
  CB_ATTEMPT_CTX_LOG_TRACE(
2226
2237
  this, "after a CAS_MISMATCH or DOC_ALREADY_EXISTS, then got error {} in create_staged_insert", ec2);
2227
2238
  if (expiry_overtime_mode_.load()) {
@@ -2247,7 +2258,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2247
2258
  }
2248
2259
  return get_doc(
2249
2260
  id,
2250
- [this, id, content, cb = std::forward<Handler>(cb), error_handler, delay](
2261
+ [this, id, content, op_id, cb = std::forward<Handler>(cb), error_handler, delay](
2251
2262
  std::optional<error_class> ec3, std::optional<std::string> err_message, std::optional<transaction_get_result> doc) mutable {
2252
2263
  if (!ec3) {
2253
2264
  if (doc) {
@@ -2266,7 +2277,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2266
2277
  CB_ATTEMPT_CTX_LOG_DEBUG(
2267
2278
  this, "create staged insert found existing deleted doc, retrying with cas {}", doc->cas().value());
2268
2279
  delay();
2269
- return create_staged_insert(id, content, doc->cas().value(), delay, std::forward<Handler>(cb));
2280
+ return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
2270
2281
  }
2271
2282
  if (!doc->links().is_document_in_transaction()) {
2272
2283
  // doc was inserted outside txn elsewhere
@@ -2275,6 +2286,18 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2275
2286
  std::forward<Handler>(cb),
2276
2287
  document_exists({ couchbase::errc::transaction_op::document_exists_exception, key_value_error_context() }));
2277
2288
  }
2289
+ if (doc->links().staged_attempt_id() == this->id()) {
2290
+ if (doc->links().staged_operation_id() == op_id) {
2291
+ // this is us dealing with resolving an ambiguity. So, lets just update the staged_mutation with the
2292
+ // correct cas and continue...
2293
+ staged_mutations_->add(staged_mutation(*doc, content, staged_mutation_type::INSERT));
2294
+ return op_completed_with_callback(std::forward<Handler>(cb), doc);
2295
+ }
2296
+ return op_completed_with_error(
2297
+ std::forward<Handler>(cb),
2298
+ transaction_operation_failed(FAIL_OTHER, "concurrent operations on a document are not allowed")
2299
+ .cause(CONCURRENT_OPERATIONS_DETECTED_ON_SAME_DOCUMENT));
2300
+ }
2278
2301
  // CBD-3787 - Only a staged insert is ok to overwrite
2279
2302
  if (doc->links().op() && *doc->links().op() != "insert") {
2280
2303
  return op_completed_with_error(
@@ -2285,7 +2308,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2285
2308
  check_and_handle_blocking_transactions(
2286
2309
  *doc,
2287
2310
  forward_compat_stage::WWC_INSERTING,
2288
- [this, id, content, doc, cb = std::forward<Handler>(cb), delay](
2311
+ [this, id, op_id, content, doc, cb = std::forward<Handler>(cb), delay](
2289
2312
  std::optional<transaction_operation_failed> err) mutable {
2290
2313
  if (err) {
2291
2314
  return op_completed_with_error(std::move(cb), *err);
@@ -2293,7 +2316,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2293
2316
  CB_ATTEMPT_CTX_LOG_DEBUG(
2294
2317
  this, "doc ok to overwrite, retrying create_staged_insert with cas {}", doc->cas().value());
2295
2318
  delay();
2296
- return create_staged_insert(id, content, doc->cas().value(), delay, std::forward<Handler>(cb));
2319
+ return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
2297
2320
  });
2298
2321
  } else {
2299
2322
  // no doc now, just retry entire txn
@@ -2320,20 +2343,27 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2320
2343
  const std::vector<std::byte>& content,
2321
2344
  uint64_t cas,
2322
2345
  Delay&& delay,
2346
+ const std::string& op_id,
2323
2347
  Handler&& cb)
2324
2348
  {
2325
2349
 
2326
2350
  if (auto ec = error_if_expired_and_not_in_overtime(STAGE_CREATE_STAGED_INSERT, id.key()); ec) {
2327
- return create_staged_insert_error_handler(
2328
- id, content, cas, std::forward<Delay>(delay), std::forward<Handler>(cb), *ec, "create_staged_insert expired and not in overtime");
2351
+ return create_staged_insert_error_handler(id,
2352
+ content,
2353
+ cas,
2354
+ std::forward<Delay>(delay),
2355
+ op_id,
2356
+ std::forward<Handler>(cb),
2357
+ *ec,
2358
+ "create_staged_insert expired and not in overtime");
2329
2359
  }
2330
2360
 
2331
2361
  if (auto ec = hooks_.before_staged_insert(this, id.key()); ec) {
2332
2362
  return create_staged_insert_error_handler(
2333
- id, content, cas, std::forward<Delay>(delay), std::forward<Handler>(cb), *ec, "before_staged_insert hook threw error");
2363
+ id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, "before_staged_insert hook threw error");
2334
2364
  }
2335
2365
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "about to insert staged doc {} with cas {}", id, cas);
2336
- auto req = create_staging_request(id, nullptr, "insert", content);
2366
+ auto req = create_staging_request(id, nullptr, "insert", op_id, content);
2337
2367
  req.access_deleted = true;
2338
2368
  req.create_as_deleted = true;
2339
2369
  req.cas = couchbase::cas(cas);
@@ -2341,11 +2371,13 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2341
2371
  wrap_durable_request(req, overall_.config());
2342
2372
  overall_.cluster_ref()->execute(
2343
2373
  req,
2344
- [this, id, content, cas, cb = std::forward<Handler>(cb), delay = std::forward<Delay>(delay)](
2374
+ [this, id, content, cas, op_id, cb = std::forward<Handler>(cb), delay = std::forward<Delay>(delay)](
2345
2375
  core::operations::mutate_in_response resp) mutable {
2346
- if (auto ec = hooks_.after_staged_insert_complete(this, id.key()); ec) {
2376
+ auto ec = resp.ctx.ec() ? error_class_from_response(resp) : hooks_.after_staged_insert_complete(this, id.key());
2377
+ if (ec) {
2378
+ auto msg = (resp.ctx.ec() ? resp.ctx.ec().message() : "after_staged_insert hook threw error");
2347
2379
  return create_staged_insert_error_handler(
2348
- id, content, cas, std::forward<Delay>(delay), std::forward<Handler>(cb), *ec, "after_staged_insert hook threw error");
2380
+ id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, msg);
2349
2381
  }
2350
2382
  if (!resp.ctx.ec()) {
2351
2383
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "inserted doc {} CAS={}, {}", id, resp.cas.value(), resp.ctx.ec().message());
@@ -2357,6 +2389,7 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2357
2389
  id.collection(),
2358
2390
  overall_.transaction_id(),
2359
2391
  this->id(),
2392
+ op_id,
2360
2393
  content,
2361
2394
  std::nullopt,
2362
2395
  std::nullopt,
@@ -2373,6 +2406,7 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2373
2406
  content,
2374
2407
  cas,
2375
2408
  std::forward<Delay>(delay),
2409
+ op_id,
2376
2410
  std::forward<Handler>(cb),
2377
2411
  error_class_from_response(resp).value(),
2378
2412
  resp.ctx.ec().message());
@@ -301,11 +301,23 @@ class attempt_context_impl
301
301
  existing_error();
302
302
  return func();
303
303
  } catch (const async_operation_conflict& e) {
304
- // can't do anything here but log and eat it.
305
304
  CB_ATTEMPT_CTX_LOG_ERROR(this, "Attempted to perform txn operation after commit/rollback started: {}", e.what());
306
305
  // you cannot call op_completed_with_error, as it tries to decrement
307
306
  // the op count, however it didn't successfully increment it, so...
308
- op_completed_with_error_no_cache(std::move(cb), std::current_exception());
307
+ auto err = transaction_operation_failed(FAIL_OTHER, "async operation conflict");
308
+ switch (state()) {
309
+ case attempt_state::ABORTED:
310
+ case attempt_state::ROLLED_BACK:
311
+ err.cause(TRANSACTION_ALREADY_ABORTED);
312
+ break;
313
+ case attempt_state::COMMITTED:
314
+ case attempt_state::COMPLETED:
315
+ err.cause(TRANSACTION_ALREADY_COMMITTED);
316
+ break;
317
+ default:
318
+ err.cause(UNKNOWN);
319
+ }
320
+ op_completed_with_error_no_cache(std::move(cb), std::make_exception_ptr(err));
309
321
  } catch (const transaction_operation_failed& e) {
310
322
  // thrown only from call_func when previous error exists, so eat it, unless
311
323
  // it has PREVIOUS_OP_FAILED cause
@@ -389,26 +401,25 @@ class attempt_context_impl
389
401
  std::optional<std::string> query_context,
390
402
  couchbase::transactions::async_query_handler&& handler) override
391
403
  {
392
- query(
393
- statement,
394
- opts,
395
- query_context,
396
- [handler = std::move(handler)](std::exception_ptr err, std::optional<core::operations::query_response> resp) {
397
- if (err) {
398
- try {
399
- std::rethrow_exception(err);
400
- } catch (const transaction_operation_failed& e) {
401
- return handler(e.get_error_ctx(), {});
402
- } catch (const op_exception& ex) {
403
- return handler(ex.ctx(), {});
404
- } catch (...) {
405
- // just in case...
406
- return handler(transaction_op_error_context(couchbase::errc::transaction_op::unknown), {});
404
+ query(statement,
405
+ opts,
406
+ query_context,
407
+ [handler = std::move(handler)](std::exception_ptr err, std::optional<core::operations::query_response> resp) {
408
+ if (err) {
409
+ try {
410
+ std::rethrow_exception(err);
411
+ } catch (const transaction_operation_failed& e) {
412
+ return handler(e.get_error_ctx(), {});
413
+ } catch (const op_exception& ex) {
414
+ return handler(ex.ctx(), {});
415
+ } catch (...) {
416
+ // just in case...
417
+ return handler(transaction_op_error_context(couchbase::errc::transaction_op::unknown), {});
418
+ }
407
419
  }
408
- }
409
- auto [ctx, res] = core::impl::build_transaction_query_result(*resp);
410
- handler(ctx, res);
411
- });
420
+ auto [ctx, res] = core::impl::build_transaction_query_result(*resp);
421
+ handler(ctx, res);
422
+ });
412
423
  }
413
424
 
414
425
  void commit() override;
@@ -445,7 +456,7 @@ class attempt_context_impl
445
456
 
446
457
  void state(attempt_state s)
447
458
  {
448
- overall_.current_attempt().state = s;
459
+ overall_.current_attempt_state(s);
449
460
  }
450
461
 
451
462
  [[nodiscard]] const std::string atr_id()
@@ -514,6 +525,7 @@ class attempt_context_impl
514
525
  core::operations::mutate_in_request create_staging_request(const core::document_id& in,
515
526
  const transaction_get_result* document,
516
527
  const std::string type,
528
+ const std::string op_id,
517
529
  std::optional<std::vector<std::byte>> content = std::nullopt);
518
530
 
519
531
  template<typename Handler, typename Delay>
@@ -521,16 +533,21 @@ class attempt_context_impl
521
533
  const std::vector<std::byte>& content,
522
534
  uint64_t cas,
523
535
  Delay&& delay,
536
+ const std::string& op_id,
524
537
  Handler&& cb);
525
538
 
526
539
  template<typename Handler>
527
- void create_staged_replace(const transaction_get_result& document, const std::vector<std::byte>& content, Handler&& cb);
540
+ void create_staged_replace(const transaction_get_result& document,
541
+ const std::vector<std::byte>& content,
542
+ const std::string& op_id,
543
+ Handler&& cb);
528
544
 
529
545
  template<typename Handler, typename Delay>
530
546
  void create_staged_insert_error_handler(const core::document_id& id,
531
547
  const std::vector<std::byte>& content,
532
548
  uint64_t cas,
533
549
  Delay&& delay,
550
+ const std::string& op_id,
534
551
  Handler&& cb,
535
552
  error_class ec,
536
553
  const std::string& message);
@@ -601,6 +618,10 @@ class attempt_context_impl
601
618
 
602
619
  void ensure_open_bucket(std::string bucket_name, std::function<void(std::error_code)>&& handler)
603
620
  {
621
+ if (bucket_name.empty()) {
622
+ CB_LOG_DEBUG("ensure_open_bucket called with empty bucket_name");
623
+ return handler(couchbase::errc::common::bucket_not_found);
624
+ }
604
625
  cluster_ref()->open_bucket(bucket_name, [handler = std::move(handler)](std::error_code ec) { handler(ec); });
605
626
  }
606
627
  };
@@ -122,8 +122,8 @@ struct forward_compat_behavior_full {
122
122
  struct forward_compat_supported {
123
123
  uint32_t protocol_major = 2;
124
124
  uint32_t protocol_minor = 0;
125
- std::list<std::string> extensions{ "TI", "MO", "BM", "QU", "SD", "BF3787", "BF3705", "BF3838",
126
- "RC", "UA", "CO", "BF3791", "CM", "SI", "QC", "IX" };
125
+ std::list<std::string> extensions{ "TI", "MO", "BM", "QU", "SD", "BF3787", "BF3705", "BF3838", "RC",
126
+ "UA", "CO", "BF3791", "CM", "SI", "QC", "IX", "TS" };
127
127
  };
128
128
 
129
129
  struct forward_compat_requirement {
@@ -49,30 +49,29 @@ class transaction_context
49
49
 
50
50
  [[nodiscard]] std::size_t num_attempts() const
51
51
  {
52
+ std::lock_guard<std::mutex> lock(mutex_);
52
53
  return attempts_.size();
53
54
  }
54
- [[nodiscard]] const std::vector<transaction_attempt>& attempts() const
55
- {
56
- return attempts_;
57
- }
58
- [[nodiscard]] std::vector<transaction_attempt>& attempts()
59
- {
60
- return const_cast<std::vector<transaction_attempt>&>(const_cast<const transaction_context*>(this)->attempts());
61
- }
62
55
  [[nodiscard]] const transaction_attempt& current_attempt() const
63
56
  {
57
+ std::lock_guard<std::mutex> lock(mutex_);
64
58
  if (attempts_.empty()) {
65
59
  throw std::runtime_error("transaction context has no attempts yet");
66
60
  }
67
61
  return attempts_.back();
68
62
  }
69
- [[nodiscard]] transaction_attempt& current_attempt()
70
- {
71
- return const_cast<transaction_attempt&>(const_cast<const transaction_context*>(this)->current_attempt());
72
- }
73
63
 
74
64
  void add_attempt();
75
65
 
66
+ void current_attempt_state(attempt_state s)
67
+ {
68
+ std::lock_guard<std::mutex> lock(mutex_);
69
+ if (attempts_.empty()) {
70
+ throw std::runtime_error("transaction_context has no attempts yet");
71
+ }
72
+ attempts_.back().state = s;
73
+ }
74
+
76
75
  [[nodiscard]] std::shared_ptr<core::cluster> cluster_ref()
77
76
  {
78
77
  return transactions_.cluster_ref();
@@ -90,7 +89,7 @@ class transaction_context
90
89
 
91
90
  [[nodiscard]] bool has_expired_client_side();
92
91
 
93
- void retry_delay();
92
+ void after_delay(std::chrono::milliseconds delay, std::function<void()> fn);
94
93
 
95
94
  [[nodiscard]] std::chrono::time_point<std::chrono::steady_clock> start_time_client() const
96
95
  {
@@ -191,6 +190,7 @@ class transaction_context
191
190
  std::string atr_collection_;
192
191
  transactions_cleanup& cleanup_;
193
192
  std::shared_ptr<attempt_context_impl> current_attempt_context_;
193
+ mutable std::mutex mutex_;
194
194
 
195
195
  std::unique_ptr<exp_delay> delay_;
196
196
  };
@@ -50,6 +50,7 @@ static const std::string TRANSACTION_RESTORE_PREFIX_ONLY = TRANSACTION_INTERFACE
50
50
  static const std::string TRANSACTION_RESTORE_PREFIX = TRANSACTION_RESTORE_PREFIX_ONLY + ".";
51
51
  static const std::string TRANSACTION_ID = TRANSACTION_INTERFACE_PREFIX + "id.txn";
52
52
  static const std::string ATTEMPT_ID = TRANSACTION_INTERFACE_PREFIX + "id.atmpt";
53
+ static const std::string OPERATION_ID = TRANSACTION_INTERFACE_PREFIX + "id.op";
53
54
  static const std::string ATR_ID = TRANSACTION_INTERFACE_PREFIX + "atr.id";
54
55
  static const std::string ATR_BUCKET_NAME = TRANSACTION_INTERFACE_PREFIX + "atr.bkt";
55
56
 
@@ -145,6 +145,12 @@ class transactions_cleanup
145
145
 
146
146
  void attempts_loop();
147
147
 
148
+ bool is_running()
149
+ {
150
+ std::unique_lock<std::mutex> lock(mutex_);
151
+ return running_;
152
+ }
153
+
148
154
  template<class R, class P>
149
155
  bool interruptable_wait(std::chrono::duration<R, P> time);
150
156
 
@@ -153,7 +159,7 @@ class transactions_cleanup
153
159
  void create_client_record(const couchbase::transactions::transaction_keyspace& keyspace);
154
160
  const atr_cleanup_stats handle_atr_cleanup(const core::document_id& atr_id,
155
161
  std::vector<transactions_cleanup_attempt>* result = nullptr);
156
- std::atomic<bool> running_{ false };
162
+ bool running_{ false };
157
163
  };
158
164
  } // namespace transactions
159
165
  } // namespace couchbase::core
@@ -315,7 +315,7 @@ staged_mutation_queue::commit_doc(attempt_context_impl* ctx, staged_mutation& it
315
315
 
316
316
  result res;
317
317
  if (item.type() == staged_mutation_type::INSERT && !cas_zero_mode) {
318
- core::operations::insert_request req{ item.doc().id(), item.doc().content() };
318
+ core::operations::insert_request req{ item.doc().id(), item.content() };
319
319
  req.flags = couchbase::codec::codec_flags::json_common_flags;
320
320
  wrap_durable_request(req, ctx->overall_.config());
321
321
  auto barrier = std::make_shared<std::promise<result>>();
@@ -17,9 +17,9 @@
17
17
  #pragma once
18
18
 
19
19
  #include "attempt_context_impl.hxx"
20
-
21
20
  #include "internal/utils.hxx"
22
21
  #include "transaction_get_result.hxx"
22
+ #include "uid_generator.hxx"
23
23
 
24
24
  #include <mutex>
25
25
  #include <string>
@@ -35,13 +35,18 @@ class staged_mutation
35
35
  transaction_get_result doc_;
36
36
  staged_mutation_type type_;
37
37
  std::vector<std::byte> content_;
38
+ std::string operation_id_;
38
39
 
39
40
  public:
40
41
  template<typename Content>
41
- staged_mutation(transaction_get_result& doc, Content content, staged_mutation_type type)
42
+ staged_mutation(transaction_get_result& doc,
43
+ Content content,
44
+ staged_mutation_type type,
45
+ std::string operation_id = uid_generator::next())
42
46
  : doc_(doc)
43
47
  , type_(type)
44
48
  , content_(std::move(content))
49
+ , operation_id_(std::move(operation_id))
45
50
  {
46
51
  }
47
52
 
@@ -97,6 +102,11 @@ class staged_mutation
97
102
  }
98
103
  throw std::runtime_error("unknown type of staged mutation");
99
104
  }
105
+
106
+ [[nodiscard]] const std::string& operation_id() const
107
+ {
108
+ return operation_id_;
109
+ }
100
110
  };
101
111
 
102
112
  class staged_mutation_queue
@@ -17,7 +17,7 @@
17
17
  #include "attempt_context_impl.hxx"
18
18
  #include "uid_generator.hxx"
19
19
  #include <asio/post.hpp>
20
- #include <core/cluster.hxx>
20
+ #include <asio/steady_timer.hpp>
21
21
 
22
22
  #include "internal/logging.hxx"
23
23
  #include "internal/transaction_context.hxx"
@@ -45,6 +45,7 @@ void
45
45
  transaction_context::add_attempt()
46
46
  {
47
47
  transaction_attempt attempt{};
48
+ std::lock_guard<std::mutex> lock(mutex_);
48
49
  attempts_.push_back(attempt);
49
50
  }
50
51
 
@@ -78,17 +79,14 @@ transaction_context::has_expired_client_side()
78
79
  }
79
80
 
80
81
  void
81
- transaction_context::retry_delay()
82
+ transaction_context::after_delay(std::chrono::milliseconds delay, std::function<void()> fn)
82
83
  {
83
- // when we retry an operation, we typically call that function recursively. So, we need to
84
- // limit total number of times we do it. CXXCBC-263 will address this, and no longer make the
85
- // recursive calls that lead to hacks like this. No way to know how many calls will blow up the
86
- // stack, so using 50 in hopes that makes jenkins happy till we do this better.
87
- constexpr auto arbitrary_factor = 50;
88
- auto delay = config_.expiration_time / arbitrary_factor;
89
- CB_ATTEMPT_CTX_LOG_TRACE(
90
- current_attempt_context_, "about to sleep for {} ms", std::chrono::duration_cast<std::chrono::milliseconds>(delay).count());
91
- std::this_thread::sleep_for(delay);
84
+ auto timer = std::make_shared<asio::steady_timer>(this->transactions_.cluster_ref()->io_context());
85
+ timer->expires_after(delay);
86
+ timer->async_wait([timer, fn](std::error_code) {
87
+ // have to always call the function, even if timer was canceled.
88
+ fn();
89
+ });
92
90
  }
93
91
 
94
92
  void