couchbase 3.4.3 → 3.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/couchbase/CMakeLists.txt +15 -1
  4. data/ext/couchbase/core/bucket.cxx +183 -152
  5. data/ext/couchbase/core/bucket.hxx +17 -4
  6. data/ext/couchbase/core/cluster.hxx +34 -13
  7. data/ext/couchbase/core/cluster_options.hxx +3 -0
  8. data/ext/couchbase/core/crud_component.cxx +51 -22
  9. data/ext/couchbase/core/error_context/key_value.cxx +2 -1
  10. data/ext/couchbase/core/error_context/key_value.hxx +10 -12
  11. data/ext/couchbase/core/impl/build_deferred_query_indexes.cxx +115 -50
  12. data/ext/couchbase/core/impl/cluster.cxx +6 -0
  13. data/ext/couchbase/core/impl/create_bucket.cxx +155 -0
  14. data/ext/couchbase/core/impl/create_query_index.cxx +172 -59
  15. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +2 -1
  16. data/ext/couchbase/core/impl/drop_bucket.cxx +66 -0
  17. data/ext/couchbase/core/impl/drop_query_index.cxx +138 -59
  18. data/ext/couchbase/core/impl/flush_bucket.cxx +66 -0
  19. data/ext/couchbase/core/impl/get_all_buckets.cxx +163 -0
  20. data/ext/couchbase/core/impl/get_all_query_indexes.cxx +67 -37
  21. data/ext/couchbase/core/impl/get_bucket.cxx +153 -0
  22. data/ext/couchbase/core/impl/internal_manager_error_context.cxx +113 -0
  23. data/ext/couchbase/core/impl/internal_manager_error_context.hxx +60 -0
  24. data/ext/couchbase/core/impl/key_value_error_category.cxx +2 -4
  25. data/ext/couchbase/core/impl/key_value_error_context.cxx +98 -0
  26. data/ext/couchbase/core/impl/lookup_in.cxx +1 -0
  27. data/ext/couchbase/core/impl/lookup_in_all_replicas.cxx +176 -0
  28. data/ext/couchbase/core/impl/lookup_in_all_replicas.hxx +80 -0
  29. data/ext/couchbase/core/impl/lookup_in_any_replica.cxx +167 -0
  30. data/ext/couchbase/core/impl/lookup_in_any_replica.hxx +75 -0
  31. data/ext/couchbase/core/impl/lookup_in_replica.cxx +97 -0
  32. data/ext/couchbase/core/impl/lookup_in_replica.hxx +67 -0
  33. data/ext/couchbase/core/impl/manager_error_context.cxx +100 -0
  34. data/ext/couchbase/core/impl/query.cxx +1 -0
  35. data/ext/couchbase/core/impl/query_error_context.cxx +75 -0
  36. data/ext/couchbase/core/impl/update_bucket.cxx +130 -0
  37. data/ext/couchbase/core/impl/watch_query_indexes.cxx +53 -29
  38. data/ext/couchbase/core/io/dns_client.cxx +111 -40
  39. data/ext/couchbase/core/io/dns_config.cxx +5 -4
  40. data/ext/couchbase/core/io/http_session.hxx +24 -1
  41. data/ext/couchbase/core/io/mcbp_command.hxx +9 -2
  42. data/ext/couchbase/core/io/mcbp_session.cxx +80 -43
  43. data/ext/couchbase/core/io/mcbp_session.hxx +4 -3
  44. data/ext/couchbase/core/logger/custom_rotating_file_sink.cxx +1 -1
  45. data/ext/couchbase/core/logger/logger.cxx +80 -20
  46. data/ext/couchbase/core/logger/logger.hxx +31 -0
  47. data/ext/couchbase/core/meta/features.hxx +25 -0
  48. data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +192 -0
  49. data/ext/couchbase/core/operations/document_lookup_in_any_replica.hxx +188 -0
  50. data/ext/couchbase/core/operations/document_query.cxx +11 -0
  51. data/ext/couchbase/core/operations/document_query.hxx +1 -0
  52. data/ext/couchbase/core/operations.hxx +2 -0
  53. data/ext/couchbase/core/origin.cxx +270 -0
  54. data/ext/couchbase/core/origin.hxx +2 -0
  55. data/ext/couchbase/core/protocol/client_response.hxx +1 -0
  56. data/ext/couchbase/core/protocol/cmd_hello.hxx +1 -0
  57. data/ext/couchbase/core/protocol/cmd_lookup_in_replica.cxx +107 -0
  58. data/ext/couchbase/core/protocol/cmd_lookup_in_replica.hxx +137 -0
  59. data/ext/couchbase/core/protocol/hello_feature.hxx +6 -0
  60. data/ext/couchbase/core/protocol/hello_feature_fmt.hxx +3 -0
  61. data/ext/couchbase/core/protocol/status.cxx +2 -2
  62. data/ext/couchbase/core/range_scan_options.cxx +3 -27
  63. data/ext/couchbase/core/range_scan_options.hxx +13 -17
  64. data/ext/couchbase/core/range_scan_orchestrator.cxx +388 -170
  65. data/ext/couchbase/core/range_scan_orchestrator.hxx +13 -2
  66. data/ext/couchbase/core/range_scan_orchestrator_options.hxx +5 -3
  67. data/ext/couchbase/core/scan_options.hxx +0 -19
  68. data/ext/couchbase/core/scan_result.cxx +19 -5
  69. data/ext/couchbase/core/scan_result.hxx +5 -2
  70. data/ext/couchbase/core/timeout_defaults.hxx +2 -3
  71. data/ext/couchbase/core/topology/capabilities.hxx +3 -0
  72. data/ext/couchbase/core/topology/capabilities_fmt.hxx +8 -0
  73. data/ext/couchbase/core/topology/collections_manifest_fmt.hxx +1 -1
  74. data/ext/couchbase/core/topology/configuration.hxx +15 -0
  75. data/ext/couchbase/core/topology/configuration_json.hxx +6 -1
  76. data/ext/couchbase/core/utils/connection_string.cxx +62 -47
  77. data/ext/couchbase/core/utils/connection_string.hxx +1 -0
  78. data/ext/couchbase/couchbase/analytics_error_context.hxx +1 -1
  79. data/ext/couchbase/couchbase/behavior_options.hxx +19 -2
  80. data/ext/couchbase/couchbase/bucket_manager.hxx +135 -0
  81. data/ext/couchbase/couchbase/build_query_index_options.hxx +0 -30
  82. data/ext/couchbase/couchbase/cluster.hxx +14 -0
  83. data/ext/couchbase/couchbase/collection.hxx +111 -0
  84. data/ext/couchbase/couchbase/collection_query_index_manager.hxx +7 -48
  85. data/ext/couchbase/couchbase/create_bucket_options.hxx +41 -0
  86. data/ext/couchbase/couchbase/create_primary_query_index_options.hxx +0 -29
  87. data/ext/couchbase/couchbase/create_query_index_options.hxx +0 -33
  88. data/ext/couchbase/couchbase/drop_bucket_options.hxx +41 -0
  89. data/ext/couchbase/couchbase/drop_primary_query_index_options.hxx +0 -30
  90. data/ext/couchbase/couchbase/drop_query_index_options.hxx +0 -31
  91. data/ext/couchbase/couchbase/error_codes.hxx +1 -2
  92. data/ext/couchbase/couchbase/error_context.hxx +10 -2
  93. data/ext/couchbase/couchbase/flush_bucket_options.hxx +41 -0
  94. data/ext/couchbase/{core/topology/error_map_fmt.hxx → couchbase/fmt/key_value_error_map_attribute.hxx} +21 -21
  95. data/ext/couchbase/couchbase/get_all_buckets_options.hxx +44 -0
  96. data/ext/couchbase/couchbase/get_all_query_indexes_options.hxx +0 -30
  97. data/ext/couchbase/couchbase/get_and_lock_options.hxx +2 -2
  98. data/ext/couchbase/couchbase/get_and_touch_options.hxx +2 -2
  99. data/ext/couchbase/couchbase/get_bucket_options.hxx +43 -0
  100. data/ext/couchbase/couchbase/get_options.hxx +2 -2
  101. data/ext/couchbase/couchbase/insert_options.hxx +3 -3
  102. data/ext/couchbase/couchbase/key_value_error_context.hxx +7 -2
  103. data/ext/couchbase/couchbase/lookup_in_all_replicas_options.hxx +109 -0
  104. data/ext/couchbase/couchbase/lookup_in_any_replica_options.hxx +101 -0
  105. data/ext/couchbase/couchbase/lookup_in_options.hxx +2 -2
  106. data/ext/couchbase/couchbase/lookup_in_replica_result.hxx +74 -0
  107. data/ext/couchbase/couchbase/lookup_in_result.hxx +26 -0
  108. data/ext/couchbase/couchbase/management/bucket_settings.hxx +116 -0
  109. data/ext/couchbase/couchbase/manager_error_context.hxx +29 -53
  110. data/ext/couchbase/couchbase/mutate_in_options.hxx +2 -2
  111. data/ext/couchbase/couchbase/query_error_context.hxx +3 -1
  112. data/ext/couchbase/couchbase/query_index_manager.hxx +16 -83
  113. data/ext/couchbase/couchbase/query_options.hxx +18 -0
  114. data/ext/couchbase/couchbase/remove_options.hxx +2 -2
  115. data/ext/couchbase/couchbase/replace_options.hxx +3 -3
  116. data/ext/couchbase/couchbase/security_options.hxx +15 -0
  117. data/ext/couchbase/couchbase/subdocument_error_context.hxx +4 -2
  118. data/ext/couchbase/couchbase/touch_options.hxx +2 -2
  119. data/ext/couchbase/couchbase/unlock_options.hxx +2 -2
  120. data/ext/couchbase/couchbase/update_bucket_options.hxx +41 -0
  121. data/ext/couchbase/couchbase/upsert_options.hxx +3 -3
  122. data/ext/couchbase/couchbase/watch_query_indexes_options.hxx +0 -31
  123. data/ext/couchbase/test/CMakeLists.txt +1 -0
  124. data/ext/couchbase/test/test_integration_collections.cxx +6 -0
  125. data/ext/couchbase/test/test_integration_crud.cxx +5 -0
  126. data/ext/couchbase/test/test_integration_examples.cxx +137 -1
  127. data/ext/couchbase/test/test_integration_management.cxx +709 -266
  128. data/ext/couchbase/test/test_integration_query.cxx +19 -7
  129. data/ext/couchbase/test/test_integration_range_scan.cxx +351 -112
  130. data/ext/couchbase/test/test_integration_search.cxx +10 -1
  131. data/ext/couchbase/test/test_integration_subdoc.cxx +655 -0
  132. data/ext/couchbase/test/test_transaction_public_async_api.cxx +13 -12
  133. data/ext/couchbase/test/test_transaction_public_blocking_api.cxx +27 -21
  134. data/ext/couchbase/test/test_unit_connection_string.cxx +29 -0
  135. data/ext/couchbase/test/test_unit_query.cxx +75 -0
  136. data/ext/couchbase.cxx +583 -29
  137. data/ext/revisions.rb +3 -3
  138. data/lib/couchbase/cluster.rb +1 -1
  139. data/lib/couchbase/collection.rb +108 -0
  140. data/lib/couchbase/collection_options.rb +100 -0
  141. data/lib/couchbase/errors.rb +5 -0
  142. data/lib/couchbase/key_value_scan.rb +125 -0
  143. data/lib/couchbase/options.rb +151 -0
  144. data/lib/couchbase/scope.rb +1 -1
  145. data/lib/couchbase/utils/time.rb +14 -1
  146. data/lib/couchbase/version.rb +1 -1
  147. metadata +41 -7
  148. data/ext/couchbase/core/impl/collection_query_index_manager.cxx +0 -93
@@ -40,12 +40,12 @@ populate_documents_for_range_scan(const couchbase::collection& collection,
40
40
  options.expiry(expiry.value());
41
41
  }
42
42
 
43
- std::map<std::vector<std::byte>, couchbase::mutation_token> mutations;
43
+ std::map<std::string, couchbase::mutation_token> mutations;
44
44
  for (const auto& id : ids) {
45
45
  auto [ctx, resp] = collection.upsert<couchbase::codec::raw_binary_transcoder>(id, value, options).get();
46
46
  REQUIRE_SUCCESS(ctx.ec());
47
47
  REQUIRE(resp.mutation_token().has_value());
48
- mutations[couchbase::core::utils::to_binary(id)] = resp.mutation_token().value();
48
+ mutations[id] = resp.mutation_token().value();
49
49
  }
50
50
  return mutations;
51
51
  }
@@ -76,7 +76,6 @@ do_range_scan(couchbase::core::agent agent,
76
76
  std::vector<couchbase::core::range_scan_item> data;
77
77
 
78
78
  auto options = continue_options;
79
- options.ids_only = create_options.ids_only; // support servers before MB-54267. TODO: remove after server GA
80
79
 
81
80
  do {
82
81
  auto barrier = std::make_shared<std::promise<std::pair<couchbase::core::range_scan_continue_result, std::error_code>>>();
@@ -115,6 +114,26 @@ make_binary_value(std::size_t number_of_bytes)
115
114
  return value;
116
115
  }
117
116
 
117
+ static couchbase::core::topology::configuration::vbucket_map
118
+ get_vbucket_map(test::utils::integration_test_guard& integration)
119
+ {
120
+ auto barrier = std::make_shared<std::promise<tl::expected<couchbase::core::topology::configuration::vbucket_map, std::error_code>>>();
121
+ auto f = barrier->get_future();
122
+ integration.cluster->with_bucket_configuration(
123
+ integration.ctx.bucket, [barrier](std::error_code ec, const couchbase::core::topology::configuration& config) mutable {
124
+ if (ec) {
125
+ return barrier->set_value(tl::unexpected(ec));
126
+ }
127
+ if (!config.vbmap || config.vbmap->empty()) {
128
+ return barrier->set_value(tl::unexpected(couchbase::errc::common::feature_not_available));
129
+ }
130
+ barrier->set_value(config.vbmap.value());
131
+ });
132
+ auto vbucket_map = f.get();
133
+ EXPECT_SUCCESS(vbucket_map);
134
+ return vbucket_map.value();
135
+ }
136
+
118
137
  TEST_CASE("integration: range scan large values", "[integration]")
119
138
  {
120
139
  test::utils::integration_test_guard integration;
@@ -148,8 +167,8 @@ TEST_CASE("integration: range scan large values", "[integration]")
148
167
  couchbase::scope::default_name,
149
168
  couchbase::collection::default_name,
150
169
  couchbase::core::range_scan{
151
- { couchbase::core::utils::to_binary("largevalues") },
152
- { couchbase::core::utils::to_binary("largevalues\xff") },
170
+ couchbase::core::scan_term{ "largevalues" },
171
+ couchbase::core::scan_term{ "largevalues\xff" },
153
172
  },
154
173
  };
155
174
  create_options.snapshot_requirements = couchbase::core::range_snapshot_requirements{
@@ -208,8 +227,8 @@ TEST_CASE("integration: range scan small values", "[integration]")
208
227
  couchbase::scope::default_name,
209
228
  couchbase::collection::default_name,
210
229
  couchbase::core::range_scan{
211
- { couchbase::core::utils::to_binary("rangesmallvalues") },
212
- { couchbase::core::utils::to_binary("rangesmallvalues\xff") },
230
+ couchbase::core::scan_term{ "rangesmallvalues" },
231
+ couchbase::core::scan_term{ "rangesmallvalues\xff" },
213
232
  },
214
233
  };
215
234
  create_options.snapshot_requirements = couchbase::core::range_snapshot_requirements{
@@ -312,8 +331,8 @@ TEST_CASE("integration: range scan collection retry", "[integration]")
312
331
  couchbase::scope::default_name,
313
332
  new_collection.name(),
314
333
  couchbase::core::range_scan{
315
- { couchbase::core::utils::to_binary("rangecollectionretry") },
316
- { couchbase::core::utils::to_binary("rangecollectionretry\xff") },
334
+ couchbase::core::scan_term{ "rangecollectionretry" },
335
+ couchbase::core::scan_term{ "rangecollectionretry\xff" },
317
336
  },
318
337
  };
319
338
  create_options.snapshot_requirements = couchbase::core::range_snapshot_requirements{
@@ -372,8 +391,8 @@ TEST_CASE("integration: range scan only keys", "[integration]")
372
391
  couchbase::scope::default_name,
373
392
  couchbase::collection::default_name,
374
393
  couchbase::core::range_scan{
375
- { couchbase::core::utils::to_binary("rangekeysonly") },
376
- { couchbase::core::utils::to_binary("rangekeysonly\xff") },
394
+ couchbase::core::scan_term{ "rangekeysonly" },
395
+ couchbase::core::scan_term{ "rangekeysonly\xff" },
377
396
  },
378
397
  };
379
398
  create_options.ids_only = true;
@@ -435,8 +454,8 @@ TEST_CASE("integration: range scan cancellation before continue", "[integration]
435
454
  couchbase::scope::default_name,
436
455
  couchbase::collection::default_name,
437
456
  couchbase::core::range_scan{
438
- { couchbase::core::utils::to_binary("rangescancancel") },
439
- { couchbase::core::utils::to_binary("rangescancancel\xff") },
457
+ couchbase::core::scan_term{ "rangescancancel" },
458
+ couchbase::core::scan_term{ "rangescancancel\xff" },
440
459
  },
441
460
  };
442
461
  options.ids_only = true;
@@ -474,7 +493,6 @@ TEST_CASE("integration: range scan cancellation before continue", "[integration]
474
493
 
475
494
  couchbase::core::range_scan_continue_options options{};
476
495
  options.batch_time_limit = std::chrono::seconds{ 10 };
477
- options.ids_only = true; // support servers before MB-54267. TODO: remove after server GA
478
496
 
479
497
  bool items_callback_invoked{ false };
480
498
  {
@@ -539,8 +557,8 @@ TEST_CASE("integration: range scan cancel during streaming using protocol cancel
539
557
  couchbase::scope::default_name,
540
558
  couchbase::collection::default_name,
541
559
  couchbase::core::range_scan{
542
- { couchbase::core::utils::to_binary("rangescancancel") },
543
- { couchbase::core::utils::to_binary("rangescancancel\xff") },
560
+ couchbase::core::scan_term{ "rangescancancel" },
561
+ couchbase::core::scan_term{ "rangescancancel\xff" },
544
562
  },
545
563
  };
546
564
  options.ids_only = true;
@@ -577,7 +595,6 @@ TEST_CASE("integration: range scan cancel during streaming using protocol cancel
577
595
  couchbase::core::range_scan_continue_options options{};
578
596
  options.batch_time_limit = std::chrono::seconds{ 10 };
579
597
  options.batch_item_limit = 3; // limit batch to 3 items, while range expected to be larger
580
- options.ids_only = true; // support servers before MB-54267. TODO: remove after server GA
581
598
 
582
599
  auto barrier = std::make_shared<std::promise<std::pair<couchbase::core::range_scan_continue_result, std::error_code>>>();
583
600
  auto f = barrier->get_future();
@@ -651,8 +668,8 @@ TEST_CASE("integration: range scan cancel during streaming using pending_operati
651
668
  couchbase::scope::default_name,
652
669
  couchbase::collection::default_name,
653
670
  couchbase::core::range_scan{
654
- { couchbase::core::utils::to_binary("rangescancancel") },
655
- { couchbase::core::utils::to_binary("rangescancancel\xff") },
671
+ couchbase::core::scan_term{ "rangescancancel" },
672
+ couchbase::core::scan_term{ "rangescancancel\xff" },
656
673
  },
657
674
  };
658
675
  options.ids_only = true;
@@ -685,7 +702,6 @@ TEST_CASE("integration: range scan cancel during streaming using pending_operati
685
702
  couchbase::core::range_scan_continue_options options{};
686
703
  options.batch_time_limit = std::chrono::seconds{ 10 };
687
704
  options.batch_item_limit = 3; // limit batch to 3 items, while range expected to be larger
688
- options.ids_only = true; // support servers before MB-54267. TODO: remove after server GA
689
705
 
690
706
  auto barrier = std::make_shared<std::promise<std::pair<couchbase::core::range_scan_continue_result, std::error_code>>>();
691
707
  auto f = barrier->get_future();
@@ -774,7 +790,7 @@ make_doc_ids(std::size_t number_of_keys, const std::string& prefix)
774
790
  }
775
791
 
776
792
  static auto
777
- mutations_to_mutation_state(std::map<std::vector<std::byte>, couchbase::mutation_token> mutations)
793
+ mutations_to_mutation_state(std::map<std::string, couchbase::mutation_token> mutations)
778
794
  {
779
795
  couchbase::core::mutation_state state;
780
796
  for (const auto& [key, token] : mutations) {
@@ -800,42 +816,27 @@ TEST_CASE("integration: manager scan range without content", "[integration]")
800
816
  auto value = make_binary_value(1);
801
817
  auto mutations = populate_documents_for_range_scan(collection, ids, value, std::chrono::seconds{ 30 });
802
818
 
803
- auto barrier = std::make_shared<std::promise<tl::expected<std::size_t, std::error_code>>>();
804
- auto f = barrier->get_future();
805
- integration.cluster->with_bucket_configuration(
806
- integration.ctx.bucket, [barrier](std::error_code ec, const couchbase::core::topology::configuration& config) mutable {
807
- if (ec) {
808
- return barrier->set_value(tl::unexpected(ec));
809
- }
810
- if (!config.vbmap || config.vbmap->empty()) {
811
- return barrier->set_value(tl::unexpected(couchbase::errc::common::feature_not_available));
812
- }
813
- barrier->set_value(config.vbmap->size());
814
- });
815
- auto number_of_vbuckets = f.get();
816
- EXPECT_SUCCESS(number_of_vbuckets);
819
+ auto vbucket_map = get_vbucket_map(integration);
817
820
 
818
821
  auto ag = couchbase::core::agent_group(integration.io, { { integration.cluster } });
819
822
  ag.open_bucket(integration.ctx.bucket);
820
823
  auto agent = ag.get_agent(integration.ctx.bucket);
821
824
  REQUIRE(agent.has_value());
822
825
 
823
- couchbase::core::range_scan scan{ "rangescanwithoutcontent", "rangescanwithoutcontent\xff" };
826
+ couchbase::core::range_scan scan{
827
+ couchbase::core::scan_term{ "rangescanwithoutcontent" },
828
+ couchbase::core::scan_term{ "rangescanwithoutcontent\xff" },
829
+ };
824
830
  couchbase::core::range_scan_orchestrator_options options{};
825
831
  options.consistent_with = mutations_to_mutation_state(mutations);
826
832
  options.ids_only = true;
827
- couchbase::core::range_scan_orchestrator orchestrator(integration.io,
828
- agent.value(),
829
- number_of_vbuckets.value(),
830
- couchbase::scope::default_name,
831
- couchbase::collection::default_name,
832
- scan,
833
- options);
833
+ couchbase::core::range_scan_orchestrator orchestrator(
834
+ integration.io, agent.value(), vbucket_map, couchbase::scope::default_name, couchbase::collection::default_name, scan, options);
834
835
 
835
836
  auto result = orchestrator.scan();
836
837
  EXPECT_SUCCESS(result);
837
838
 
838
- std::set<std::vector<std::byte>> entry_ids{};
839
+ std::set<std::string> entry_ids{};
839
840
 
840
841
  do {
841
842
  auto entry = result->next();
@@ -848,10 +849,11 @@ TEST_CASE("integration: manager scan range without content", "[integration]")
848
849
  REQUIRE_FALSE(entry->body.has_value());
849
850
  } while (true);
850
851
 
852
+ REQUIRE(ids.size() == entry_ids.size());
853
+
851
854
  for (const auto& id : ids) {
852
- REQUIRE(entry_ids.count(couchbase::core::utils::to_binary(id)) == 1);
855
+ REQUIRE(entry_ids.count(id) == 1);
853
856
  }
854
- REQUIRE(ids.size() == entry_ids.size());
855
857
  }
856
858
 
857
859
  TEST_CASE("integration: manager scan range with content", "[integration]")
@@ -871,41 +873,26 @@ TEST_CASE("integration: manager scan range with content", "[integration]")
871
873
  auto value = make_binary_value(100);
872
874
  auto mutations = populate_documents_for_range_scan(collection, ids, value, std::chrono::seconds{ 30 });
873
875
 
874
- auto barrier = std::make_shared<std::promise<tl::expected<std::size_t, std::error_code>>>();
875
- auto f = barrier->get_future();
876
- integration.cluster->with_bucket_configuration(
877
- integration.ctx.bucket, [barrier](std::error_code ec, const couchbase::core::topology::configuration& config) mutable {
878
- if (ec) {
879
- return barrier->set_value(tl::unexpected(ec));
880
- }
881
- if (!config.vbmap || config.vbmap->empty()) {
882
- return barrier->set_value(tl::unexpected(couchbase::errc::common::feature_not_available));
883
- }
884
- barrier->set_value(config.vbmap->size());
885
- });
886
- auto number_of_vbuckets = f.get();
887
- EXPECT_SUCCESS(number_of_vbuckets);
876
+ auto vbucket_map = get_vbucket_map(integration);
888
877
 
889
878
  auto ag = couchbase::core::agent_group(integration.io, { { integration.cluster } });
890
879
  ag.open_bucket(integration.ctx.bucket);
891
880
  auto agent = ag.get_agent(integration.ctx.bucket);
892
881
  REQUIRE(agent.has_value());
893
882
 
894
- couchbase::core::range_scan scan{ "rangescanwithcontent", "rangescanwithcontent\xff" };
883
+ couchbase::core::range_scan scan{
884
+ couchbase::core::scan_term{ "rangescanwithcontent" },
885
+ couchbase::core::scan_term{ "rangescanwithcontent\xff" },
886
+ };
895
887
  couchbase::core::range_scan_orchestrator_options options{};
896
888
  options.consistent_with = mutations_to_mutation_state(mutations);
897
- couchbase::core::range_scan_orchestrator orchestrator(integration.io,
898
- agent.value(),
899
- number_of_vbuckets.value(),
900
- couchbase::scope::default_name,
901
- couchbase::collection::default_name,
902
- scan,
903
- options);
889
+ couchbase::core::range_scan_orchestrator orchestrator(
890
+ integration.io, agent.value(), vbucket_map, couchbase::scope::default_name, couchbase::collection::default_name, scan, options);
904
891
 
905
892
  auto result = orchestrator.scan();
906
893
  EXPECT_SUCCESS(result);
907
894
 
908
- std::set<std::vector<std::byte>> entry_ids{};
895
+ std::set<std::string> entry_ids{};
909
896
 
910
897
  do {
911
898
  auto entry = result->next();
@@ -918,11 +905,12 @@ TEST_CASE("integration: manager scan range with content", "[integration]")
918
905
  REQUIRE(entry->body.has_value());
919
906
  } while (true);
920
907
 
908
+ REQUIRE(ids.size() == entry_ids.size());
909
+
921
910
  for (const auto& id : ids) {
922
911
  INFO(id);
923
- REQUIRE(entry_ids.count(couchbase::core::utils::to_binary(id)) == 1);
912
+ REQUIRE(entry_ids.count(id) == 1);
924
913
  }
925
- REQUIRE(ids.size() == entry_ids.size());
926
914
  }
927
915
 
928
916
  TEST_CASE("integration: manager sampling scan with custom collection", "[integration]")
@@ -944,20 +932,7 @@ TEST_CASE("integration: manager sampling scan with custom collection", "[integra
944
932
  auto value = make_binary_value(100);
945
933
  auto mutations = populate_documents_for_range_scan(collection, ids, value, std::chrono::seconds{ 300 });
946
934
 
947
- auto barrier = std::make_shared<std::promise<tl::expected<std::size_t, std::error_code>>>();
948
- auto f = barrier->get_future();
949
- integration.cluster->with_bucket_configuration(
950
- integration.ctx.bucket, [barrier](std::error_code ec, const couchbase::core::topology::configuration& config) mutable {
951
- if (ec) {
952
- return barrier->set_value(tl::unexpected(ec));
953
- }
954
- if (!config.vbmap || config.vbmap->empty()) {
955
- return barrier->set_value(tl::unexpected(couchbase::errc::common::feature_not_available));
956
- }
957
- barrier->set_value(config.vbmap->size());
958
- });
959
- auto number_of_vbuckets = f.get();
960
- EXPECT_SUCCESS(number_of_vbuckets);
935
+ auto vbucket_map = get_vbucket_map(integration);
961
936
 
962
937
  auto ag = couchbase::core::agent_group(integration.io, { { integration.cluster } });
963
938
  ag.open_bucket(integration.ctx.bucket);
@@ -968,12 +943,12 @@ TEST_CASE("integration: manager sampling scan with custom collection", "[integra
968
943
  couchbase::core::range_scan_orchestrator_options options{};
969
944
  options.consistent_with = mutations_to_mutation_state(mutations);
970
945
  couchbase::core::range_scan_orchestrator orchestrator(
971
- integration.io, agent.value(), number_of_vbuckets.value(), couchbase::scope::default_name, new_collection.name(), scan, options);
946
+ integration.io, agent.value(), vbucket_map, couchbase::scope::default_name, new_collection.name(), scan, options);
972
947
 
973
948
  auto result = orchestrator.scan();
974
949
  EXPECT_SUCCESS(result);
975
950
 
976
- std::set<std::vector<std::byte>> entry_ids{};
951
+ std::set<std::string> entry_ids{};
977
952
 
978
953
  auto now = std::chrono::system_clock::now();
979
954
  do {
@@ -994,11 +969,65 @@ TEST_CASE("integration: manager sampling scan with custom collection", "[integra
994
969
  REQUIRE(ids.size() >= 10);
995
970
 
996
971
  for (const auto& id : entry_ids) {
997
- REQUIRE(std::find(ids.begin(), ids.end(), std::string(reinterpret_cast<const char*>(id.data()), id.size())) != ids.end());
972
+ REQUIRE(std::find(ids.begin(), ids.end(), id) != ids.end());
998
973
  }
999
974
  }
1000
975
 
1001
- TEST_CASE("integration: manager range scan with sort", "[integration]")
976
+ TEST_CASE("integration: manager prefix scan without content", "[integration]")
977
+ {
978
+ test::utils::integration_test_guard integration;
979
+
980
+ if (!integration.has_bucket_capability("range_scan")) {
981
+ SKIP("cluster does not support range_scan");
982
+ }
983
+
984
+ auto collection = couchbase::cluster(integration.cluster)
985
+ .bucket(integration.ctx.bucket)
986
+ .scope(couchbase::scope::default_name)
987
+ .collection(couchbase::collection::default_name);
988
+
989
+ auto ids = make_doc_ids(100, "prefixscanwithoutcontent-");
990
+ auto value = make_binary_value(1);
991
+ auto mutations = populate_documents_for_range_scan(collection, ids, value, std::chrono::seconds{ 30 });
992
+
993
+ auto vbucket_map = get_vbucket_map(integration);
994
+
995
+ auto ag = couchbase::core::agent_group(integration.io, { { integration.cluster } });
996
+ ag.open_bucket(integration.ctx.bucket);
997
+ auto agent = ag.get_agent(integration.ctx.bucket);
998
+ REQUIRE(agent.has_value());
999
+
1000
+ couchbase::core::prefix_scan scan{ "prefixscanwithoutcontent" };
1001
+ couchbase::core::range_scan_orchestrator_options options{};
1002
+ options.consistent_with = mutations_to_mutation_state(mutations);
1003
+ options.ids_only = true;
1004
+ couchbase::core::range_scan_orchestrator orchestrator(
1005
+ integration.io, agent.value(), vbucket_map, couchbase::scope::default_name, couchbase::collection::default_name, scan, options);
1006
+
1007
+ auto result = orchestrator.scan();
1008
+ EXPECT_SUCCESS(result);
1009
+
1010
+ std::set<std::string> entry_ids{};
1011
+
1012
+ do {
1013
+ auto entry = result->next();
1014
+ if (!entry) {
1015
+ break;
1016
+ }
1017
+
1018
+ auto [_, inserted] = entry_ids.insert(entry->key);
1019
+ REQUIRE(inserted);
1020
+ REQUIRE_FALSE(entry->body.has_value());
1021
+ } while (true);
1022
+
1023
+ REQUIRE(ids.size() == entry_ids.size());
1024
+
1025
+ for (const auto& id : ids) {
1026
+ REQUIRE(entry_ids.count(id) == 1);
1027
+ }
1028
+ }
1029
+
1030
+ TEST_CASE("integration: manager sampling scan with custom collection and up to 10 concurrent streams", "[integration]")
1002
1031
  {
1003
1032
  test::utils::integration_test_guard integration;
1004
1033
 
@@ -1013,43 +1042,151 @@ TEST_CASE("integration: manager range scan with sort", "[integration]")
1013
1042
  .scope(couchbase::scope::default_name)
1014
1043
  .collection(new_collection.name());
1015
1044
 
1016
- auto ids = make_doc_ids(100, "rangescansort-");
1045
+ auto ids = make_doc_ids(100, "samplingscan-");
1017
1046
  auto value = make_binary_value(100);
1018
1047
  auto mutations = populate_documents_for_range_scan(collection, ids, value, std::chrono::seconds{ 300 });
1019
1048
 
1020
- auto barrier = std::make_shared<std::promise<tl::expected<std::size_t, std::error_code>>>();
1021
- auto f = barrier->get_future();
1022
- integration.cluster->with_bucket_configuration(
1023
- integration.ctx.bucket, [barrier](std::error_code ec, const couchbase::core::topology::configuration& config) mutable {
1024
- if (ec) {
1025
- return barrier->set_value(tl::unexpected(ec));
1026
- }
1027
- if (!config.vbmap || config.vbmap->empty()) {
1028
- return barrier->set_value(tl::unexpected(couchbase::errc::common::feature_not_available));
1029
- }
1030
- barrier->set_value(config.vbmap->size());
1031
- });
1032
- auto number_of_vbuckets = f.get();
1033
- EXPECT_SUCCESS(number_of_vbuckets);
1049
+ auto vbucket_map = get_vbucket_map(integration);
1050
+
1051
+ auto ag = couchbase::core::agent_group(integration.io, { { integration.cluster } });
1052
+ ag.open_bucket(integration.ctx.bucket);
1053
+ auto agent = ag.get_agent(integration.ctx.bucket);
1054
+ REQUIRE(agent.has_value());
1055
+
1056
+ couchbase::core::sampling_scan scan{ 10, 50 };
1057
+ couchbase::core::range_scan_orchestrator_options options{};
1058
+ options.consistent_with = mutations_to_mutation_state(mutations);
1059
+ options.concurrency = 10;
1060
+ couchbase::core::range_scan_orchestrator orchestrator(
1061
+ integration.io, agent.value(), vbucket_map, couchbase::scope::default_name, new_collection.name(), scan, options);
1062
+
1063
+ auto result = orchestrator.scan();
1064
+ EXPECT_SUCCESS(result);
1065
+
1066
+ std::set<std::string> entry_ids{};
1067
+
1068
+ auto now = std::chrono::system_clock::now();
1069
+ do {
1070
+ auto entry = result->next();
1071
+ if (!entry) {
1072
+ break;
1073
+ }
1074
+
1075
+ REQUIRE(entry->body);
1076
+ REQUIRE_FALSE(entry->body->cas.empty());
1077
+ REQUIRE(entry->body->value == value);
1078
+ REQUIRE(entry->body->expiry_time() > now);
1079
+
1080
+ auto [_, inserted] = entry_ids.insert(entry->key);
1081
+ REQUIRE(inserted);
1082
+ } while (true);
1083
+
1084
+ REQUIRE(ids.size() >= 10);
1085
+
1086
+ for (const auto& id : entry_ids) {
1087
+ REQUIRE(std::find(ids.begin(), ids.end(), id) != ids.end());
1088
+ }
1089
+ }
1090
+
1091
+ TEST_CASE("integration: manager sampling scan with custom collection and up to 128 concurrent streams and batch item limit 0",
1092
+ "[integration]")
1093
+ {
1094
+ test::utils::integration_test_guard integration;
1095
+
1096
+ if (!integration.has_bucket_capability("range_scan")) {
1097
+ SKIP("cluster does not support range_scan");
1098
+ }
1099
+
1100
+ collection_guard new_collection(integration);
1101
+
1102
+ auto collection = couchbase::cluster(integration.cluster)
1103
+ .bucket(integration.ctx.bucket)
1104
+ .scope(couchbase::scope::default_name)
1105
+ .collection(new_collection.name());
1106
+
1107
+ auto ids = make_doc_ids(100, "samplingscan-");
1108
+ auto value = make_binary_value(100);
1109
+ auto mutations = populate_documents_for_range_scan(collection, ids, value, std::chrono::seconds{ 300 });
1110
+
1111
+ auto vbucket_map = get_vbucket_map(integration);
1034
1112
 
1035
1113
  auto ag = couchbase::core::agent_group(integration.io, { { integration.cluster } });
1036
1114
  ag.open_bucket(integration.ctx.bucket);
1037
1115
  auto agent = ag.get_agent(integration.ctx.bucket);
1038
1116
  REQUIRE(agent.has_value());
1039
1117
 
1040
- couchbase::core::range_scan scan{ "rangescansort", "rangescansort\xff" };
1118
+ couchbase::core::sampling_scan scan{ 10, 50 };
1119
+ couchbase::core::range_scan_orchestrator_options options{};
1120
+ options.consistent_with = mutations_to_mutation_state(mutations);
1121
+ options.concurrency = 128;
1122
+ options.batch_item_limit = 0;
1123
+ couchbase::core::range_scan_orchestrator orchestrator(
1124
+ integration.io, agent.value(), vbucket_map, couchbase::scope::default_name, new_collection.name(), scan, options);
1125
+
1126
+ auto result = orchestrator.scan();
1127
+ EXPECT_SUCCESS(result);
1128
+
1129
+ std::set<std::string> entry_ids{};
1130
+
1131
+ auto now = std::chrono::system_clock::now();
1132
+ do {
1133
+ auto entry = result->next();
1134
+ if (!entry) {
1135
+ break;
1136
+ }
1137
+
1138
+ REQUIRE(entry->body);
1139
+ REQUIRE_FALSE(entry->body->cas.empty());
1140
+ REQUIRE(entry->body->value == value);
1141
+ REQUIRE(entry->body->expiry_time() > now);
1142
+
1143
+ auto [_, inserted] = entry_ids.insert(entry->key);
1144
+ REQUIRE(inserted);
1145
+ } while (true);
1146
+
1147
+ REQUIRE(ids.size() >= 10);
1148
+
1149
+ for (const auto& id : entry_ids) {
1150
+ REQUIRE(std::find(ids.begin(), ids.end(), id) != ids.end());
1151
+ }
1152
+ }
1153
+
1154
+ TEST_CASE("integration: manager prefix scan without content and up to 5 concurrent streams", "[integration]")
1155
+ {
1156
+ test::utils::integration_test_guard integration;
1157
+
1158
+ if (!integration.has_bucket_capability("range_scan")) {
1159
+ SKIP("cluster does not support range_scan");
1160
+ }
1161
+
1162
+ auto collection = couchbase::cluster(integration.cluster)
1163
+ .bucket(integration.ctx.bucket)
1164
+ .scope(couchbase::scope::default_name)
1165
+ .collection(couchbase::collection::default_name);
1166
+
1167
+ auto ids = make_doc_ids(100, "prefixscanwithoutcontent-");
1168
+ auto value = make_binary_value(1);
1169
+ auto mutations = populate_documents_for_range_scan(collection, ids, value, std::chrono::seconds{ 30 });
1170
+
1171
+ auto vbucket_map = get_vbucket_map(integration);
1172
+
1173
+ auto ag = couchbase::core::agent_group(integration.io, { { integration.cluster } });
1174
+ ag.open_bucket(integration.ctx.bucket);
1175
+ auto agent = ag.get_agent(integration.ctx.bucket);
1176
+ REQUIRE(agent.has_value());
1177
+
1178
+ couchbase::core::prefix_scan scan{ "prefixscanwithoutcontent" };
1041
1179
  couchbase::core::range_scan_orchestrator_options options{};
1042
1180
  options.consistent_with = mutations_to_mutation_state(mutations);
1043
1181
  options.ids_only = true;
1044
- options.sort = couchbase::core::scan_sort::ascending;
1182
+ options.concurrency = 5;
1045
1183
  couchbase::core::range_scan_orchestrator orchestrator(
1046
- integration.io, agent.value(), number_of_vbuckets.value(), couchbase::scope::default_name, new_collection.name(), scan, options);
1184
+ integration.io, agent.value(), vbucket_map, couchbase::scope::default_name, couchbase::collection::default_name, scan, options);
1047
1185
 
1048
1186
  auto result = orchestrator.scan();
1049
1187
  EXPECT_SUCCESS(result);
1050
1188
 
1051
- std::vector<std::string> entry_ids{};
1052
- entry_ids.reserve(ids.size());
1189
+ std::set<std::string> entry_ids{};
1053
1190
 
1054
1191
  do {
1055
1192
  auto entry = result->next();
@@ -1057,11 +1194,113 @@ TEST_CASE("integration: manager range scan with sort", "[integration]")
1057
1194
  break;
1058
1195
  }
1059
1196
 
1060
- entry_ids.emplace_back(reinterpret_cast<const char*>(entry->key.data()), entry->key.size());
1197
+ auto [_, inserted] = entry_ids.insert(entry->key);
1198
+ REQUIRE(inserted);
1061
1199
  REQUIRE_FALSE(entry->body.has_value());
1062
1200
  } while (true);
1063
1201
 
1064
1202
  REQUIRE(ids.size() == entry_ids.size());
1065
- std::sort(ids.begin(), ids.end());
1066
- REQUIRE(ids == entry_ids);
1203
+
1204
+ for (const auto& id : ids) {
1205
+ REQUIRE(entry_ids.count(id) == 1);
1206
+ }
1207
+ }
1208
+
1209
+ TEST_CASE("integration: manager prefix scan, get 10 items and cancel", "[integration]")
1210
+ {
1211
+ test::utils::integration_test_guard integration;
1212
+
1213
+ if (!integration.has_bucket_capability("range_scan")) {
1214
+ SKIP("cluster does not support range_scan");
1215
+ }
1216
+
1217
+ auto collection = couchbase::cluster(integration.cluster)
1218
+ .bucket(integration.ctx.bucket)
1219
+ .scope(couchbase::scope::default_name)
1220
+ .collection(couchbase::collection::default_name);
1221
+
1222
+ auto ids = make_doc_ids(15, "rangescancancel-");
1223
+ auto value = make_binary_value(1);
1224
+ auto mutations = populate_documents_for_range_scan(collection, ids, value, std::chrono::seconds{ 30 });
1225
+
1226
+ auto vbucket_map = get_vbucket_map(integration);
1227
+
1228
+ auto ag = couchbase::core::agent_group(integration.io, { { integration.cluster } });
1229
+ ag.open_bucket(integration.ctx.bucket);
1230
+ auto agent = ag.get_agent(integration.ctx.bucket);
1231
+ REQUIRE(agent.has_value());
1232
+
1233
+ couchbase::core::prefix_scan scan{ "rangescancancel" };
1234
+ couchbase::core::range_scan_orchestrator_options options{};
1235
+ options.consistent_with = mutations_to_mutation_state(mutations);
1236
+ options.ids_only = true;
1237
+ couchbase::core::range_scan_orchestrator orchestrator(
1238
+ integration.io, agent.value(), vbucket_map, couchbase::scope::default_name, couchbase::collection::default_name, scan, options);
1239
+
1240
+ auto result = orchestrator.scan();
1241
+ EXPECT_SUCCESS(result);
1242
+
1243
+ std::set<std::string> entry_ids{};
1244
+ std::size_t const expected_id_count = 10;
1245
+
1246
+ for (std::size_t i = 0; i < expected_id_count; i++) {
1247
+ auto entry = result->next();
1248
+ if (!entry) {
1249
+ break;
1250
+ }
1251
+
1252
+ auto [_, inserted] = entry_ids.insert(entry->key);
1253
+ REQUIRE(inserted);
1254
+ REQUIRE_FALSE(entry->body.has_value());
1255
+ }
1256
+
1257
+ result->cancel();
1258
+
1259
+ REQUIRE(expected_id_count == entry_ids.size());
1260
+
1261
+ for (const auto& entry_id : entry_ids) {
1262
+ REQUIRE(std::count(ids.begin(), ids.end(), entry_id) == 0);
1263
+ }
1264
+
1265
+ auto next_item = result->next();
1266
+ REQUIRE(!next_item.has_value());
1267
+ REQUIRE(next_item.error() == couchbase::errc::key_value::range_scan_completed);
1268
+ REQUIRE(result->is_cancelled());
1269
+ }
1270
+
1271
+ TEST_CASE("integration: manager prefix scan with concurrency 0 (invalid argument)", "[integration]")
1272
+ {
1273
+ test::utils::integration_test_guard integration;
1274
+
1275
+ if (!integration.has_bucket_capability("range_scan")) {
1276
+ SKIP("cluster does not support range_scan");
1277
+ }
1278
+
1279
+ auto collection = couchbase::cluster(integration.cluster)
1280
+ .bucket(integration.ctx.bucket)
1281
+ .scope(couchbase::scope::default_name)
1282
+ .collection(couchbase::collection::default_name);
1283
+
1284
+ auto ids = make_doc_ids(100, "prefixscaninvalidconcurrency-");
1285
+ auto value = make_binary_value(1);
1286
+ auto mutations = populate_documents_for_range_scan(collection, ids, value, std::chrono::seconds{ 30 });
1287
+
1288
+ auto vbucket_map = get_vbucket_map(integration);
1289
+
1290
+ auto ag = couchbase::core::agent_group(integration.io, { { integration.cluster } });
1291
+ ag.open_bucket(integration.ctx.bucket);
1292
+ auto agent = ag.get_agent(integration.ctx.bucket);
1293
+ REQUIRE(agent.has_value());
1294
+
1295
+ couchbase::core::prefix_scan scan{ "prefixscaninvalidconcurrency" };
1296
+ couchbase::core::range_scan_orchestrator_options options{};
1297
+ options.consistent_with = mutations_to_mutation_state(mutations);
1298
+ options.ids_only = true;
1299
+ options.concurrency = 0;
1300
+ couchbase::core::range_scan_orchestrator orchestrator(
1301
+ integration.io, agent.value(), vbucket_map, couchbase::scope::default_name, couchbase::collection::default_name, scan, options);
1302
+
1303
+ auto result = orchestrator.scan();
1304
+ REQUIRE(!result.has_value());
1305
+ REQUIRE(result.error() == couchbase::errc::common::invalid_argument);
1067
1306
  }