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
data/ext/couchbase.cxx CHANGED
@@ -33,9 +33,12 @@
33
33
  #include <core/platform/terminate_handler.h>
34
34
 
35
35
  #include <core/cluster.hxx>
36
+
37
+ #include <core/agent_group.hxx>
36
38
  #include <core/design_document_namespace_fmt.hxx>
37
39
  #include <core/logger/configuration.hxx>
38
40
  #include <core/operations.hxx>
41
+
39
42
  #include <core/operations/management/analytics.hxx>
40
43
  #include <core/operations/management/bucket.hxx>
41
44
  #include <core/operations/management/cluster_developer_preview_enable.hxx>
@@ -45,6 +48,12 @@
45
48
  #include <core/operations/management/user.hxx>
46
49
  #include <core/operations/management/view.hxx>
47
50
 
51
+ #include <core/impl/subdoc/path_flags.hxx>
52
+
53
+ #include <core/range_scan_options.hxx>
54
+ #include <core/range_scan_orchestrator.hxx>
55
+ #include <core/range_scan_orchestrator_options.hxx>
56
+
48
57
  #include <core/io/dns_client.hxx>
49
58
  #include <core/io/dns_config.hxx>
50
59
  #include <core/utils/connection_string.hxx>
@@ -435,7 +444,7 @@ cb_backend_close(cb_backend_data* backend)
435
444
  static void
436
445
  cb_Backend_mark(void* /* ptr */)
437
446
  {
438
- /* no embeded ruby objects -- no mark */
447
+ /* no embedded ruby objects -- no mark */
439
448
  }
440
449
 
441
450
  static void
@@ -495,6 +504,8 @@ cb_backend_to_cluster(VALUE self)
495
504
  return backend->cluster;
496
505
  }
497
506
 
507
+ static VALUE eCouchbaseError;
508
+
498
509
  static VALUE eAmbiguousTimeout;
499
510
  static VALUE eAuthenticationFailure;
500
511
  static VALUE eBucketExists;
@@ -534,6 +545,7 @@ static VALUE eInvalidArgument;
534
545
  static VALUE eJobQueueFull;
535
546
  static VALUE eLinkNotFound;
536
547
  static VALUE eLinkExists;
548
+ static VALUE eMutationTokenOutdated;
537
549
  static VALUE eNumberTooBig;
538
550
  static VALUE eParsingFailure;
539
551
  static VALUE ePathExists;
@@ -586,7 +598,7 @@ static void
586
598
  init_exceptions(VALUE mCouchbase)
587
599
  {
588
600
  VALUE mError = rb_define_module_under(mCouchbase, "Error");
589
- VALUE eCouchbaseError = rb_define_class_under(mError, "CouchbaseError", rb_eStandardError);
601
+ eCouchbaseError = rb_define_class_under(mError, "CouchbaseError", rb_eStandardError);
590
602
 
591
603
  VALUE eTimeout = rb_define_class_under(mError, "Timeout", eCouchbaseError);
592
604
 
@@ -629,6 +641,7 @@ init_exceptions(VALUE mCouchbase)
629
641
  eJobQueueFull = rb_define_class_under(mError, "JobQueueFull", eCouchbaseError);
630
642
  eLinkNotFound = rb_define_class_under(mError, "LinkNotFound", eCouchbaseError);
631
643
  eLinkExists = rb_define_class_under(mError, "LinkExists", eCouchbaseError);
644
+ eMutationTokenOutdated = rb_define_class_under(mError, "MutationTokenOutdated", eCouchbaseError);
632
645
  eNumberTooBig = rb_define_class_under(mError, "NumberTooBig", eCouchbaseError);
633
646
  eParsingFailure = rb_define_class_under(mError, "ParsingFailure", eCouchbaseError);
634
647
  ePathExists = rb_define_class_under(mError, "PathExists", eCouchbaseError);
@@ -784,6 +797,9 @@ cb_map_error_code(std::error_code ec, const std::string& message, bool include_e
784
797
  case couchbase::errc::key_value::durable_write_re_commit_in_progress:
785
798
  return rb_exc_new_cstr(eDurableWriteReCommitInProgress, what.c_str());
786
799
 
800
+ case couchbase::errc::key_value::mutation_token_outdated:
801
+ return rb_exc_new_cstr(eMutationTokenOutdated, what.c_str());
802
+
787
803
  case couchbase::errc::key_value::path_not_found:
788
804
  return rb_exc_new_cstr(ePathNotFound, what.c_str());
789
805
 
@@ -834,6 +850,10 @@ cb_map_error_code(std::error_code ec, const std::string& message, bool include_e
834
850
 
835
851
  case couchbase::errc::key_value::cannot_revive_living_document:
836
852
  return rb_exc_new_cstr(eCannotReviveLivingDocument, what.c_str());
853
+
854
+ case couchbase::errc::key_value::range_scan_completed:
855
+ // Should not be exposed to the Ruby SDK, map it to a BackendError
856
+ return rb_exc_new_cstr(eBackendError, what.c_str());
837
857
  }
838
858
  } else if (ec.category() == couchbase::core::impl::query_category()) {
839
859
  switch (static_cast<couchbase::errc::query>(ec.value())) {
@@ -3409,7 +3429,7 @@ cb_Backend_document_decrement(VALUE self, VALUE bucket, VALUE scope, VALUE colle
3409
3429
  static VALUE
3410
3430
  cb_Backend_document_lookup_in(VALUE self, VALUE bucket, VALUE scope, VALUE collection, VALUE id, VALUE specs, VALUE options)
3411
3431
  {
3412
- const auto& core = cb_backend_to_cluster(self);
3432
+ const auto& cluster = cb_backend_to_cluster(self);
3413
3433
 
3414
3434
  Check_Type(bucket, T_STRING);
3415
3435
  Check_Type(scope, T_STRING);
@@ -3425,10 +3445,6 @@ cb_Backend_document_lookup_in(VALUE self, VALUE bucket, VALUE scope, VALUE colle
3425
3445
  }
3426
3446
 
3427
3447
  try {
3428
- couchbase::lookup_in_options opts;
3429
- couchbase::ruby::set_timeout(opts, options);
3430
- couchbase::ruby::set_access_deleted(opts, options);
3431
-
3432
3448
  couchbase::core::document_id doc_id{
3433
3449
  cb_string_new(bucket),
3434
3450
  cb_string_new(scope),
@@ -3436,12 +3452,15 @@ cb_Backend_document_lookup_in(VALUE self, VALUE bucket, VALUE scope, VALUE colle
3436
3452
  cb_string_new(id),
3437
3453
  };
3438
3454
 
3455
+ couchbase::core::operations::lookup_in_request req{ doc_id };
3456
+ cb_extract_timeout(req, options);
3457
+ cb_extract_option_bool(req.access_deleted, options, "access_deleted");
3458
+
3439
3459
  static VALUE xattr_property = rb_id2sym(rb_intern("xattr"));
3440
3460
  static VALUE path_property = rb_id2sym(rb_intern("path"));
3441
3461
  static VALUE opcode_property = rb_id2sym(rb_intern("opcode"));
3442
3462
 
3443
3463
  auto entries_size = static_cast<std::size_t>(RARRAY_LEN(specs));
3444
- couchbase::lookup_in_specs cxx_specs;
3445
3464
  for (std::size_t i = 0; i < entries_size; ++i) {
3446
3465
  VALUE entry = rb_ary_entry(specs, static_cast<long>(i));
3447
3466
  cb_check_type(entry, T_HASH);
@@ -3450,29 +3469,138 @@ cb_Backend_document_lookup_in(VALUE self, VALUE bucket, VALUE scope, VALUE colle
3450
3469
  bool xattr = RTEST(rb_hash_aref(entry, xattr_property));
3451
3470
  VALUE path = rb_hash_aref(entry, path_property);
3452
3471
  cb_check_type(path, T_STRING);
3472
+ auto opcode = couchbase::core::impl::subdoc::opcode{};
3453
3473
  if (ID operation_id = rb_sym2id(operation); operation_id == rb_intern("get_doc")) {
3454
- cxx_specs.push_back(couchbase::lookup_in_specs::get("").xattr(xattr));
3474
+ opcode = couchbase::core::impl::subdoc::opcode::get_doc;
3455
3475
  } else if (operation_id == rb_intern("get")) {
3456
- cxx_specs.push_back(couchbase::lookup_in_specs::get(cb_string_new(path)).xattr(xattr));
3476
+ opcode = couchbase::core::impl::subdoc::opcode::get;
3457
3477
  } else if (operation_id == rb_intern("exists")) {
3458
- cxx_specs.push_back(couchbase::lookup_in_specs::exists(cb_string_new(path)).xattr(xattr));
3478
+ opcode = couchbase::core::impl::subdoc::opcode::exists;
3459
3479
  } else if (operation_id == rb_intern("count")) {
3460
- cxx_specs.push_back(couchbase::lookup_in_specs::count(cb_string_new(path)).xattr(xattr));
3480
+ opcode = couchbase::core::impl::subdoc::opcode::get_count;
3461
3481
  } else {
3462
3482
  throw ruby_exception(eInvalidArgument, rb_sprintf("unsupported operation for subdocument lookup: %+" PRIsVALUE, operation));
3463
3483
  }
3464
3484
  cb_check_type(path, T_STRING);
3485
+
3486
+ req.specs.emplace_back(couchbase::core::impl::subdoc::command{
3487
+ opcode, cb_string_new(path), {}, couchbase::core::impl::subdoc::build_lookup_in_path_flags(xattr) });
3465
3488
  }
3466
3489
 
3467
- auto f = couchbase::cluster(core)
3468
- .bucket(cb_string_new(bucket))
3469
- .scope(cb_string_new(scope))
3470
- .collection(cb_string_new(collection))
3471
- .lookup_in(cb_string_new(id), cxx_specs, opts);
3490
+ auto barrier = std::make_shared<std::promise<couchbase::core::operations::lookup_in_response>>();
3491
+ auto f = barrier->get_future();
3492
+ cluster->execute(req, [barrier](couchbase::core::operations::lookup_in_response&& resp) { barrier->set_value(std::move(resp)); });
3493
+ auto resp = cb_wait_for_future(f);
3494
+ if (resp.ctx.ec()) {
3495
+ cb_throw_error_code(resp.ctx, "unable to perform lookup_in operation");
3496
+ }
3472
3497
 
3473
- auto [ctx, resp] = cb_wait_for_future(f);
3474
- if (ctx.ec()) {
3475
- cb_throw_error_code(ctx, "unable to lookup_in");
3498
+ static VALUE deleted_property = rb_id2sym(rb_intern("deleted"));
3499
+ static VALUE fields_property = rb_id2sym(rb_intern("fields"));
3500
+ static VALUE index_property = rb_id2sym(rb_intern("index"));
3501
+ static VALUE exists_property = rb_id2sym(rb_intern("exists"));
3502
+ static VALUE cas_property = rb_id2sym(rb_intern("cas"));
3503
+ static VALUE value_property = rb_id2sym(rb_intern("value"));
3504
+ static VALUE error_property = rb_id2sym(rb_intern("error"));
3505
+
3506
+ VALUE res = rb_hash_new();
3507
+ rb_hash_aset(res, cas_property, cb_cas_to_num(resp.cas));
3508
+ VALUE fields = rb_ary_new_capa(static_cast<long>(entries_size));
3509
+ rb_hash_aset(res, fields_property, fields);
3510
+ rb_hash_aset(res, deleted_property, resp.deleted ? Qtrue : Qfalse);
3511
+ for (std::size_t i = 0; i < entries_size; ++i) {
3512
+ auto resp_entry = resp.fields.at(i);
3513
+ VALUE entry = rb_hash_new();
3514
+ rb_hash_aset(entry, index_property, ULL2NUM(resp_entry.original_index));
3515
+ rb_hash_aset(entry, exists_property, resp_entry.exists ? Qtrue : Qfalse);
3516
+ rb_hash_aset(entry, path_property, cb_str_new(resp_entry.path));
3517
+ if (!resp_entry.value.empty()) {
3518
+ rb_hash_aset(entry, value_property, cb_str_new(resp_entry.value));
3519
+ }
3520
+ if (resp_entry.ec && resp_entry.ec != couchbase::errc::key_value::path_not_found) {
3521
+ rb_hash_aset(entry,
3522
+ error_property,
3523
+ cb_map_error_code(resp_entry.ec,
3524
+ fmt::format("error getting result for spec at index {}, path \"{}\"", i, resp_entry.path)));
3525
+ }
3526
+ rb_ary_store(fields, static_cast<long>(i), entry);
3527
+ }
3528
+ return res;
3529
+ } catch (const std::system_error& se) {
3530
+ rb_exc_raise(cb_map_error_code(se.code(), fmt::format("failed to perform {}: {}", __func__, se.what()), false));
3531
+ } catch (const ruby_exception& e) {
3532
+ rb_exc_raise(e.exception_object());
3533
+ }
3534
+ return Qnil;
3535
+ }
3536
+
3537
+ static VALUE
3538
+ cb_Backend_document_lookup_in_any_replica(VALUE self, VALUE bucket, VALUE scope, VALUE collection, VALUE id, VALUE specs, VALUE options)
3539
+ {
3540
+ const auto& cluster = cb_backend_to_cluster(self);
3541
+
3542
+ Check_Type(bucket, T_STRING);
3543
+ Check_Type(scope, T_STRING);
3544
+ Check_Type(collection, T_STRING);
3545
+ Check_Type(id, T_STRING);
3546
+ Check_Type(specs, T_ARRAY);
3547
+ if (RARRAY_LEN(specs) <= 0) {
3548
+ rb_raise(rb_eArgError, "Array with specs cannot be empty");
3549
+ return Qnil;
3550
+ }
3551
+ if (!NIL_P(options)) {
3552
+ Check_Type(options, T_HASH);
3553
+ }
3554
+
3555
+ try {
3556
+ couchbase::core::document_id doc_id{
3557
+ cb_string_new(bucket),
3558
+ cb_string_new(scope),
3559
+ cb_string_new(collection),
3560
+ cb_string_new(id),
3561
+ };
3562
+
3563
+ couchbase::core::operations::lookup_in_any_replica_request req{ doc_id };
3564
+ cb_extract_timeout(req, options);
3565
+
3566
+ static VALUE xattr_property = rb_id2sym(rb_intern("xattr"));
3567
+ static VALUE path_property = rb_id2sym(rb_intern("path"));
3568
+ static VALUE opcode_property = rb_id2sym(rb_intern("opcode"));
3569
+
3570
+ auto entries_size = static_cast<std::size_t>(RARRAY_LEN(specs));
3571
+ for (std::size_t i = 0; i < entries_size; ++i) {
3572
+ VALUE entry = rb_ary_entry(specs, static_cast<long>(i));
3573
+ cb_check_type(entry, T_HASH);
3574
+ VALUE operation = rb_hash_aref(entry, opcode_property);
3575
+ cb_check_type(operation, T_SYMBOL);
3576
+ bool xattr = RTEST(rb_hash_aref(entry, xattr_property));
3577
+ VALUE path = rb_hash_aref(entry, path_property);
3578
+ cb_check_type(path, T_STRING);
3579
+ auto opcode = couchbase::core::impl::subdoc::opcode{};
3580
+ if (ID operation_id = rb_sym2id(operation); operation_id == rb_intern("get_doc")) {
3581
+ opcode = couchbase::core::impl::subdoc::opcode::get_doc;
3582
+ } else if (operation_id == rb_intern("get")) {
3583
+ opcode = couchbase::core::impl::subdoc::opcode::get;
3584
+ } else if (operation_id == rb_intern("exists")) {
3585
+ opcode = couchbase::core::impl::subdoc::opcode::exists;
3586
+ } else if (operation_id == rb_intern("count")) {
3587
+ opcode = couchbase::core::impl::subdoc::opcode::get_count;
3588
+ } else {
3589
+ throw ruby_exception(eInvalidArgument, rb_sprintf("unsupported operation for subdocument lookup: %+" PRIsVALUE, operation));
3590
+ }
3591
+ cb_check_type(path, T_STRING);
3592
+
3593
+ req.specs.emplace_back(couchbase::core::impl::subdoc::command{
3594
+ opcode, cb_string_new(path), {}, couchbase::core::impl::subdoc::build_lookup_in_path_flags(xattr) });
3595
+ }
3596
+
3597
+ auto barrier = std::make_shared<std::promise<couchbase::core::operations::lookup_in_any_replica_response>>();
3598
+ auto f = barrier->get_future();
3599
+ cluster->execute(
3600
+ req, [barrier](couchbase::core::operations::lookup_in_any_replica_response&& resp) { barrier->set_value(std::move(resp)); });
3601
+ auto resp = cb_wait_for_future(f);
3602
+ if (resp.ctx.ec()) {
3603
+ cb_throw_error_code(resp.ctx, "unable to perform lookup_in_any_replica operation");
3476
3604
  }
3477
3605
 
3478
3606
  static VALUE deleted_property = rb_id2sym(rb_intern("deleted"));
@@ -3481,23 +3609,153 @@ cb_Backend_document_lookup_in(VALUE self, VALUE bucket, VALUE scope, VALUE colle
3481
3609
  static VALUE exists_property = rb_id2sym(rb_intern("exists"));
3482
3610
  static VALUE cas_property = rb_id2sym(rb_intern("cas"));
3483
3611
  static VALUE value_property = rb_id2sym(rb_intern("value"));
3612
+ static VALUE error_property = rb_id2sym(rb_intern("error"));
3613
+ static VALUE is_replica_property = rb_id2sym(rb_intern("is_replica"));
3484
3614
 
3485
3615
  VALUE res = rb_hash_new();
3486
- rb_hash_aset(res, cas_property, cb_cas_to_num(resp.cas()));
3616
+ rb_hash_aset(res, cas_property, cb_cas_to_num(resp.cas));
3487
3617
  VALUE fields = rb_ary_new_capa(static_cast<long>(entries_size));
3488
3618
  rb_hash_aset(res, fields_property, fields);
3489
- rb_hash_aset(res, deleted_property, resp.is_deleted() ? Qtrue : Qfalse);
3619
+ rb_hash_aset(res, deleted_property, resp.deleted ? Qtrue : Qfalse);
3620
+ rb_hash_aset(res, is_replica_property, resp.is_replica ? Qtrue : Qfalse);
3621
+
3490
3622
  for (std::size_t i = 0; i < entries_size; ++i) {
3623
+ auto resp_entry = resp.fields.at(i);
3491
3624
  VALUE entry = rb_hash_new();
3492
- rb_hash_aset(entry, index_property, ULL2NUM(i));
3493
- rb_hash_aset(entry, exists_property, resp.exists(i) ? Qtrue : Qfalse);
3494
- rb_hash_aset(entry, path_property, rb_hash_aref(rb_ary_entry(specs, static_cast<long>(i)), path_property));
3495
- if (resp.has_value(i)) {
3496
- auto value = resp.content_as<tao::json::value>(i);
3497
- rb_hash_aset(entry, value_property, cb_str_new(couchbase::core::utils::json::generate(value)));
3625
+ rb_hash_aset(entry, index_property, ULL2NUM(resp_entry.original_index));
3626
+ rb_hash_aset(entry, exists_property, resp_entry.exists ? Qtrue : Qfalse);
3627
+ rb_hash_aset(entry, path_property, cb_str_new(resp_entry.path));
3628
+ if (!resp_entry.value.empty()) {
3629
+ rb_hash_aset(entry, value_property, cb_str_new(resp_entry.value));
3630
+ }
3631
+ if (resp_entry.ec && resp_entry.ec != couchbase::errc::key_value::path_not_found) {
3632
+ rb_hash_aset(entry,
3633
+ error_property,
3634
+ cb_map_error_code(resp_entry.ec,
3635
+ fmt::format("error getting result for spec at index {}, path \"{}\"", i, resp_entry.path)));
3498
3636
  }
3499
3637
  rb_ary_store(fields, static_cast<long>(i), entry);
3500
3638
  }
3639
+
3640
+ return res;
3641
+ } catch (const std::system_error& se) {
3642
+ rb_exc_raise(cb_map_error_code(se.code(), fmt::format("failed to perform {}: {}", __func__, se.what()), false));
3643
+ } catch (const ruby_exception& e) {
3644
+ rb_exc_raise(e.exception_object());
3645
+ }
3646
+ return Qnil;
3647
+ }
3648
+
3649
+ static VALUE
3650
+ cb_Backend_document_lookup_in_all_replicas(VALUE self, VALUE bucket, VALUE scope, VALUE collection, VALUE id, VALUE specs, VALUE options)
3651
+ {
3652
+ const auto& cluster = cb_backend_to_cluster(self);
3653
+
3654
+ Check_Type(bucket, T_STRING);
3655
+ Check_Type(scope, T_STRING);
3656
+ Check_Type(collection, T_STRING);
3657
+ Check_Type(id, T_STRING);
3658
+ Check_Type(specs, T_ARRAY);
3659
+ if (RARRAY_LEN(specs) <= 0) {
3660
+ rb_raise(rb_eArgError, "Array with specs cannot be empty");
3661
+ return Qnil;
3662
+ }
3663
+ if (!NIL_P(options)) {
3664
+ Check_Type(options, T_HASH);
3665
+ }
3666
+
3667
+ try {
3668
+ couchbase::core::document_id doc_id{
3669
+ cb_string_new(bucket),
3670
+ cb_string_new(scope),
3671
+ cb_string_new(collection),
3672
+ cb_string_new(id),
3673
+ };
3674
+
3675
+ couchbase::core::operations::lookup_in_all_replicas_request req{ doc_id };
3676
+ cb_extract_timeout(req, options);
3677
+
3678
+ static VALUE xattr_property = rb_id2sym(rb_intern("xattr"));
3679
+ static VALUE path_property = rb_id2sym(rb_intern("path"));
3680
+ static VALUE opcode_property = rb_id2sym(rb_intern("opcode"));
3681
+
3682
+ auto entries_size = static_cast<std::size_t>(RARRAY_LEN(specs));
3683
+ for (std::size_t i = 0; i < entries_size; ++i) {
3684
+ VALUE entry = rb_ary_entry(specs, static_cast<long>(i));
3685
+ cb_check_type(entry, T_HASH);
3686
+ VALUE operation = rb_hash_aref(entry, opcode_property);
3687
+ cb_check_type(operation, T_SYMBOL);
3688
+ bool xattr = RTEST(rb_hash_aref(entry, xattr_property));
3689
+ VALUE path = rb_hash_aref(entry, path_property);
3690
+ cb_check_type(path, T_STRING);
3691
+ auto opcode = couchbase::core::impl::subdoc::opcode{};
3692
+ if (ID operation_id = rb_sym2id(operation); operation_id == rb_intern("get_doc")) {
3693
+ opcode = couchbase::core::impl::subdoc::opcode::get_doc;
3694
+ } else if (operation_id == rb_intern("get")) {
3695
+ opcode = couchbase::core::impl::subdoc::opcode::get;
3696
+ } else if (operation_id == rb_intern("exists")) {
3697
+ opcode = couchbase::core::impl::subdoc::opcode::exists;
3698
+ } else if (operation_id == rb_intern("count")) {
3699
+ opcode = couchbase::core::impl::subdoc::opcode::get_count;
3700
+ } else {
3701
+ throw ruby_exception(eInvalidArgument, rb_sprintf("unsupported operation for subdocument lookup: %+" PRIsVALUE, operation));
3702
+ }
3703
+ cb_check_type(path, T_STRING);
3704
+
3705
+ req.specs.emplace_back(couchbase::core::impl::subdoc::command{
3706
+ opcode, cb_string_new(path), {}, couchbase::core::impl::subdoc::build_lookup_in_path_flags(xattr) });
3707
+ }
3708
+
3709
+ auto barrier = std::make_shared<std::promise<couchbase::core::operations::lookup_in_all_replicas_response>>();
3710
+ auto f = barrier->get_future();
3711
+ cluster->execute(
3712
+ req, [barrier](couchbase::core::operations::lookup_in_all_replicas_response&& resp) { barrier->set_value(std::move(resp)); });
3713
+ auto resp = cb_wait_for_future(f);
3714
+ if (resp.ctx.ec()) {
3715
+ cb_throw_error_code(resp.ctx, "unable to perform lookup_in_all_replicas operation");
3716
+ }
3717
+
3718
+ static VALUE deleted_property = rb_id2sym(rb_intern("deleted"));
3719
+ static VALUE fields_property = rb_id2sym(rb_intern("fields"));
3720
+ static VALUE index_property = rb_id2sym(rb_intern("index"));
3721
+ static VALUE exists_property = rb_id2sym(rb_intern("exists"));
3722
+ static VALUE cas_property = rb_id2sym(rb_intern("cas"));
3723
+ static VALUE value_property = rb_id2sym(rb_intern("value"));
3724
+ static VALUE error_property = rb_id2sym(rb_intern("error"));
3725
+ static VALUE is_replica_property = rb_id2sym(rb_intern("is_replica"));
3726
+
3727
+ auto lookup_in_entries_size = resp.entries.size();
3728
+ VALUE res = rb_ary_new_capa(static_cast<long>(lookup_in_entries_size));
3729
+ for (std::size_t j = 0; j < lookup_in_entries_size; ++j) {
3730
+ auto lookup_in_entry = resp.entries.at(j);
3731
+ VALUE lookup_in_entry_res = rb_hash_new();
3732
+ rb_hash_aset(lookup_in_entry_res, cas_property, cb_cas_to_num(lookup_in_entry.cas));
3733
+ VALUE fields = rb_ary_new_capa(static_cast<long>(entries_size));
3734
+ rb_hash_aset(lookup_in_entry_res, fields_property, fields);
3735
+ rb_hash_aset(lookup_in_entry_res, deleted_property, lookup_in_entry.deleted ? Qtrue : Qfalse);
3736
+ rb_hash_aset(lookup_in_entry_res, is_replica_property, lookup_in_entry.is_replica ? Qtrue : Qfalse);
3737
+
3738
+ for (std::size_t i = 0; i < entries_size; ++i) {
3739
+ auto field_entry = lookup_in_entry.fields.at(i);
3740
+ VALUE entry = rb_hash_new();
3741
+ rb_hash_aset(entry, index_property, ULL2NUM(field_entry.original_index));
3742
+ rb_hash_aset(entry, exists_property, field_entry.exists ? Qtrue : Qfalse);
3743
+ rb_hash_aset(entry, path_property, cb_str_new(field_entry.path));
3744
+ if (!field_entry.value.empty()) {
3745
+ rb_hash_aset(entry, value_property, cb_str_new(field_entry.value));
3746
+ }
3747
+ if (field_entry.ec && field_entry.ec != couchbase::errc::key_value::path_not_found) {
3748
+ rb_hash_aset(
3749
+ entry,
3750
+ error_property,
3751
+ cb_map_error_code(field_entry.ec,
3752
+ fmt::format("error getting result for spec at index {}, path \"{}\"", i, field_entry.path)));
3753
+ }
3754
+ rb_ary_store(fields, static_cast<long>(i), entry);
3755
+ }
3756
+ rb_ary_store(res, static_cast<long>(j), lookup_in_entry_res);
3757
+ }
3758
+
3501
3759
  return res;
3502
3760
  } catch (const std::system_error& se) {
3503
3761
  rb_exc_raise(cb_map_error_code(se.code(), fmt::format("failed to perform {}: {}", __func__, se.what()), false));
@@ -3661,6 +3919,284 @@ cb_Backend_document_mutate_in(VALUE self, VALUE bucket, VALUE scope, VALUE colle
3661
3919
  return Qnil;
3662
3920
  }
3663
3921
 
3922
+ struct cb_core_scan_result_data {
3923
+ std::unique_ptr<couchbase::core::scan_result> scan_result{};
3924
+ };
3925
+
3926
+ static void
3927
+ cb_CoreScanResult_mark(void* ptr)
3928
+ {
3929
+ /* No embedded Ruby objects */
3930
+ }
3931
+
3932
+ static void
3933
+ cb_CoreScanResult_free(void* ptr)
3934
+ {
3935
+ auto* data = static_cast<cb_core_scan_result_data*>(ptr);
3936
+ if (data->scan_result != nullptr && !data->scan_result->is_cancelled()) {
3937
+ data->scan_result->cancel();
3938
+ }
3939
+ data->scan_result.reset();
3940
+ ruby_xfree(data);
3941
+ }
3942
+
3943
+ static const rb_data_type_t cb_core_scan_result_type {
3944
+ .wrap_struct_name = "Couchbase/Backend/CoreScanResult",
3945
+ .function = {
3946
+ .dmark = cb_CoreScanResult_mark,
3947
+ .dfree = cb_CoreScanResult_free,
3948
+ },
3949
+ .data = nullptr,
3950
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
3951
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
3952
+ #endif
3953
+ };
3954
+
3955
+ static VALUE
3956
+ cb_CoreScanResult_allocate(VALUE klass)
3957
+ {
3958
+ cb_core_scan_result_data* data = nullptr;
3959
+ VALUE obj = TypedData_Make_Struct(klass, cb_core_scan_result_data, &cb_core_scan_result_type, data);
3960
+ return obj;
3961
+ }
3962
+
3963
+ static VALUE
3964
+ cb_CoreScanResult_is_cancelled(VALUE self)
3965
+ {
3966
+ cb_core_scan_result_data* data = nullptr;
3967
+ TypedData_Get_Struct(self, cb_core_scan_result_data, &cb_core_scan_result_type, data);
3968
+ auto resp = data->scan_result->is_cancelled();
3969
+ if (resp) {
3970
+ return Qtrue;
3971
+ } else {
3972
+ return Qfalse;
3973
+ }
3974
+ }
3975
+
3976
+ static VALUE
3977
+ cb_CoreScanResult_cancel(VALUE self)
3978
+ {
3979
+ cb_core_scan_result_data* data = nullptr;
3980
+ TypedData_Get_Struct(self, cb_core_scan_result_data, &cb_core_scan_result_type, data);
3981
+ data->scan_result->cancel();
3982
+ return Qnil;
3983
+ }
3984
+
3985
+ static VALUE
3986
+ cb_CoreScanResult_next_item(VALUE self)
3987
+ {
3988
+ try {
3989
+ cb_core_scan_result_data* data = nullptr;
3990
+ TypedData_Get_Struct(self, cb_core_scan_result_data, &cb_core_scan_result_type, data);
3991
+ auto barrier = std::make_shared<std::promise<tl::expected<couchbase::core::range_scan_item, std::error_code>>>();
3992
+ auto f = barrier->get_future();
3993
+ data->scan_result->next([barrier](couchbase::core::range_scan_item item, std::error_code ec) {
3994
+ if (ec) {
3995
+ return barrier->set_value(tl::unexpected(ec));
3996
+ } else {
3997
+ return barrier->set_value(item);
3998
+ }
3999
+ });
4000
+ auto resp = cb_wait_for_future(f);
4001
+ if (!resp.has_value()) {
4002
+ // If the error code is range_scan_completed return nil without raising an exception (nil signifies that there
4003
+ // are no more items)
4004
+ if (resp.error() != couchbase::errc::key_value::range_scan_completed) {
4005
+ cb_throw_error_code(resp.error(), "unable to fetch next scan item");
4006
+ }
4007
+ // Release ownership of scan_result unique pointer
4008
+ return Qnil;
4009
+ }
4010
+ auto item = resp.value();
4011
+ VALUE res = rb_hash_new();
4012
+ rb_hash_aset(res, rb_id2sym(rb_intern("id")), cb_str_new(item.key));
4013
+ if (item.body.has_value()) {
4014
+ auto body = item.body.value();
4015
+ rb_hash_aset(res, rb_id2sym(rb_intern("id")), cb_str_new(item.key));
4016
+ rb_hash_aset(res, rb_id2sym(rb_intern("encoded")), cb_str_new(body.value));
4017
+ rb_hash_aset(res, rb_id2sym(rb_intern("cas")), cb_cas_to_num(body.cas));
4018
+ rb_hash_aset(res, rb_id2sym(rb_intern("flags")), UINT2NUM(body.flags));
4019
+ rb_hash_aset(res, rb_id2sym(rb_intern("expiry")), UINT2NUM(body.expiry));
4020
+ rb_hash_aset(res, rb_id2sym(rb_intern("id_only")), Qfalse);
4021
+ } else {
4022
+ rb_hash_aset(res, rb_id2sym(rb_intern("id_only")), Qtrue);
4023
+ }
4024
+ return res;
4025
+ } catch (const std::system_error& se) {
4026
+ rb_exc_raise(cb_map_error_code(se.code(), fmt::format("failed to perform {}: {}", __func__, se.what()), false));
4027
+ } catch (const ruby_exception& e) {
4028
+ rb_exc_raise(e.exception_object());
4029
+ }
4030
+ return Qnil;
4031
+ }
4032
+
4033
+ static VALUE
4034
+ cb_Backend_document_scan_create(VALUE self, VALUE bucket, VALUE scope, VALUE collection, VALUE scan_type, VALUE options)
4035
+ {
4036
+ const auto& cluster = cb_backend_to_cluster(self);
4037
+
4038
+ Check_Type(bucket, T_STRING);
4039
+ Check_Type(scope, T_STRING);
4040
+ Check_Type(collection, T_STRING);
4041
+ Check_Type(scan_type, T_HASH);
4042
+ if (!NIL_P(options)) {
4043
+ Check_Type(options, T_HASH);
4044
+ }
4045
+
4046
+ try {
4047
+ couchbase::core::range_scan_orchestrator_options orchestrator_options{};
4048
+ cb_extract_timeout(orchestrator_options, options);
4049
+ cb_extract_option_bool(orchestrator_options.ids_only, options, "ids_only");
4050
+ cb_extract_option_number(orchestrator_options.batch_item_limit, options, "batch_item_limit");
4051
+ cb_extract_option_number(orchestrator_options.batch_byte_limit, options, "batch_byte_limit");
4052
+ cb_extract_option_number(orchestrator_options.concurrency, options, "concurrency");
4053
+
4054
+ // Extracting the mutation state
4055
+ if (VALUE mutation_state = rb_hash_aref(options, rb_id2sym(rb_intern("mutation_state"))); !NIL_P(mutation_state)) {
4056
+ cb_check_type(mutation_state, T_ARRAY);
4057
+ auto state_size = static_cast<std::size_t>(RARRAY_LEN(mutation_state));
4058
+
4059
+ if (state_size > 0) {
4060
+ auto core_mut_state = couchbase::core::mutation_state{};
4061
+ core_mut_state.tokens.reserve(state_size);
4062
+ for (std::size_t i = 0; i < state_size; ++i) {
4063
+ VALUE token = rb_ary_entry(mutation_state, static_cast<long>(i));
4064
+ cb_check_type(token, T_HASH);
4065
+ VALUE bucket_name = rb_hash_aref(token, rb_id2sym(rb_intern("bucket_name")));
4066
+ cb_check_type(bucket_name, T_STRING);
4067
+ VALUE partition_id = rb_hash_aref(token, rb_id2sym(rb_intern("partition_id")));
4068
+ cb_check_type(partition_id, T_FIXNUM);
4069
+ VALUE partition_uuid = rb_hash_aref(token, rb_id2sym(rb_intern("partition_uuid")));
4070
+ switch (TYPE(partition_uuid)) {
4071
+ case T_FIXNUM:
4072
+ case T_BIGNUM:
4073
+ break;
4074
+ default:
4075
+ rb_raise(rb_eArgError, "partition_uuid must be an Integer");
4076
+ }
4077
+ VALUE sequence_number = rb_hash_aref(token, rb_id2sym(rb_intern("sequence_number")));
4078
+ switch (TYPE(sequence_number)) {
4079
+ case T_FIXNUM:
4080
+ case T_BIGNUM:
4081
+ break;
4082
+ default:
4083
+ rb_raise(rb_eArgError, "sequence_number must be an Integer");
4084
+ }
4085
+ core_mut_state.tokens.emplace_back(NUM2ULL(partition_uuid),
4086
+ NUM2ULL(sequence_number),
4087
+ gsl::narrow_cast<std::uint16_t>(NUM2UINT(partition_id)),
4088
+ cb_string_new(bucket_name));
4089
+ }
4090
+
4091
+ orchestrator_options.consistent_with = core_mut_state;
4092
+ }
4093
+ }
4094
+
4095
+ auto bucket_name = cb_string_new(bucket);
4096
+ auto scope_name = cb_string_new(scope);
4097
+ auto collection_name = cb_string_new(collection);
4098
+
4099
+ // Getting the operation agent
4100
+ auto agent_group = couchbase::core::agent_group(cluster->io_context(), couchbase::core::agent_group_config{ { cluster } });
4101
+ agent_group.open_bucket(bucket_name);
4102
+ auto agent = agent_group.get_agent(bucket_name);
4103
+ if (!agent.has_value()) {
4104
+ rb_raise(eCouchbaseError, "Cannot perform scan operation. Unable to get operation agent");
4105
+ return Qnil;
4106
+ }
4107
+
4108
+ // Getting the vbucket map
4109
+ auto barrier = std::make_shared<std::promise<tl::expected<couchbase::core::topology::configuration, std::error_code>>>();
4110
+ auto f = barrier->get_future();
4111
+ cluster->with_bucket_configuration(bucket_name,
4112
+ [barrier](std::error_code ec, const couchbase::core::topology::configuration& config) mutable {
4113
+ if (ec) {
4114
+ return barrier->set_value(tl::unexpected(ec));
4115
+ }
4116
+ barrier->set_value(config);
4117
+ });
4118
+ auto config = cb_wait_for_future(f);
4119
+ if (!config.has_value()) {
4120
+ rb_raise(eCouchbaseError, "Cannot perform scan operation. Unable to get bucket configuration");
4121
+ return Qnil;
4122
+ }
4123
+ if (!config->supports_range_scan()) {
4124
+ rb_raise(eFeatureNotAvailable, "Server does not support key-value scan operations");
4125
+ return Qnil;
4126
+ }
4127
+ auto vbucket_map = config->vbmap;
4128
+ if (!vbucket_map || vbucket_map->empty()) {
4129
+ rb_raise(eCouchbaseError, "Cannot perform scan operation. Unable to get vbucket map");
4130
+ return Qnil;
4131
+ }
4132
+
4133
+ // Constructing the scan type
4134
+ std::variant<std::monostate, couchbase::core::range_scan, couchbase::core::prefix_scan, couchbase::core::sampling_scan>
4135
+ core_scan_type{};
4136
+ ID scan_type_id = rb_sym2id(rb_hash_aref(scan_type, rb_id2sym(rb_intern("scan_type"))));
4137
+ if (scan_type_id == rb_intern("range")) {
4138
+ auto range_scan = couchbase::core::range_scan{};
4139
+
4140
+ VALUE from_hash = rb_hash_aref(scan_type, rb_id2sym(rb_intern("from")));
4141
+ VALUE to_hash = rb_hash_aref(scan_type, rb_id2sym(rb_intern("to")));
4142
+
4143
+ if (!NIL_P(from_hash)) {
4144
+ Check_Type(from_hash, T_HASH);
4145
+ range_scan.from = couchbase::core::scan_term{};
4146
+ cb_extract_option_string(range_scan.from->term, from_hash, "term");
4147
+ cb_extract_option_bool(range_scan.from->exclusive, from_hash, "exclusive");
4148
+ }
4149
+ if (!NIL_P(to_hash)) {
4150
+ Check_Type(to_hash, T_HASH);
4151
+ range_scan.to = couchbase::core::scan_term{};
4152
+ cb_extract_option_string(range_scan.to->term, to_hash, "term");
4153
+ cb_extract_option_bool(range_scan.to->exclusive, to_hash, "exclusive");
4154
+ }
4155
+ core_scan_type = range_scan;
4156
+ } else if (scan_type_id == rb_intern("prefix")) {
4157
+ auto prefix_scan = couchbase::core::prefix_scan{};
4158
+ cb_extract_option_string(prefix_scan.prefix, scan_type, "prefix");
4159
+ core_scan_type = prefix_scan;
4160
+ } else if (scan_type_id == rb_intern("sampling")) {
4161
+ auto sampling_scan = couchbase::core::sampling_scan{};
4162
+ cb_extract_option_number(sampling_scan.limit, scan_type, "limit");
4163
+ cb_extract_option_number(sampling_scan.seed, scan_type, "seed");
4164
+ core_scan_type = sampling_scan;
4165
+ } else {
4166
+ rb_raise(eInvalidArgument, "Invalid scan operation type");
4167
+ }
4168
+
4169
+ auto orchestrator = couchbase::core::range_scan_orchestrator(
4170
+ cluster->io_context(), agent.value(), vbucket_map.value(), scope_name, collection_name, core_scan_type, orchestrator_options);
4171
+
4172
+ // Start the scan
4173
+ auto resp = orchestrator.scan();
4174
+ if (!resp.has_value()) {
4175
+ cb_throw_error_code(resp.error(), "unable to start scan");
4176
+ }
4177
+
4178
+ // Wrap core scan_result inside Ruby ScanResult
4179
+ // Creating a Ruby CoreScanResult object *after* checking that no error occurred during orchestrator.scan()
4180
+ VALUE cCoreScanResult = rb_define_class_under(rb_define_module("Couchbase"), "CoreScanResult", rb_cObject);
4181
+ rb_define_alloc_func(cCoreScanResult, cb_CoreScanResult_allocate);
4182
+ rb_define_method(cCoreScanResult, "next_item", VALUE_FUNC(cb_CoreScanResult_next_item), 0);
4183
+ rb_define_method(cCoreScanResult, "cancelled?", VALUE_FUNC(cb_CoreScanResult_is_cancelled), 0);
4184
+ rb_define_method(cCoreScanResult, "cancel", VALUE_FUNC(cb_CoreScanResult_cancel), 0);
4185
+ VALUE core_scan_result_obj = rb_class_new_instance(0, NULL, cCoreScanResult);
4186
+ rb_ivar_set(core_scan_result_obj, rb_intern("@backend"), self);
4187
+ cb_core_scan_result_data* data = nullptr;
4188
+ TypedData_Get_Struct(core_scan_result_obj, cb_core_scan_result_data, &cb_core_scan_result_type, data);
4189
+ data->scan_result = std::make_unique<couchbase::core::scan_result>(resp.value());
4190
+ return core_scan_result_obj;
4191
+
4192
+ } catch (const std::system_error& se) {
4193
+ rb_exc_raise(cb_map_error_code(se.code(), fmt::format("failed to perform {}: {}", __func__, se.what()), false));
4194
+ } catch (const ruby_exception& e) {
4195
+ rb_exc_raise(e.exception_object());
4196
+ }
4197
+ return Qnil;
4198
+ }
4199
+
3664
4200
  static int
3665
4201
  cb_for_each_named_param(VALUE key, VALUE value, VALUE arg)
3666
4202
  {
@@ -3696,6 +4232,7 @@ cb_Backend_document_query(VALUE self, VALUE statement, VALUE options)
3696
4232
  cb_extract_option_bool(req.readonly, options, "readonly");
3697
4233
  cb_extract_option_bool(req.flex_index, options, "flex_index");
3698
4234
  cb_extract_option_bool(req.preserve_expiry, options, "preserve_expiry");
4235
+ cb_extract_option_bool(req.use_replica, options, "use_replica");
3699
4236
  cb_extract_option_uint64(req.scan_cap, options, "scan_cap");
3700
4237
  cb_extract_duration(req.scan_wait, options, "scan_wait");
3701
4238
  cb_extract_option_uint64(req.max_parallelism, options, "max_parallelism");
@@ -8577,10 +9114,20 @@ cb_Backend_form_encode(VALUE self, VALUE data)
8577
9114
  return cb_str_new(encoded);
8578
9115
  }
8579
9116
 
9117
+ static VALUE
9118
+ cb_Backend_enable_protocol_logger_to_save_network_traffic_to_file(VALUE /* self */, VALUE path)
9119
+ {
9120
+ Check_Type(path, T_STRING);
9121
+ couchbase::core::logger::configuration configuration{};
9122
+ configuration.filename = cb_string_new(path);
9123
+ couchbase::core::logger::create_protocol_logger(configuration);
9124
+ return Qnil;
9125
+ }
9126
+
8580
9127
  static void
8581
9128
  init_backend(VALUE mCouchbase)
8582
9129
  {
8583
- VALUE cBackend = rb_define_class_under(mCouchbase, "Backend", rb_cBasicObject);
9130
+ VALUE cBackend = rb_define_class_under(mCouchbase, "Backend", rb_cObject);
8584
9131
  rb_define_alloc_func(cBackend, cb_Backend_allocate);
8585
9132
  rb_define_method(cBackend, "open", VALUE_FUNC(cb_Backend_open), 3);
8586
9133
  rb_define_method(cBackend, "close", VALUE_FUNC(cb_Backend_close), 0);
@@ -8604,7 +9151,10 @@ init_backend(VALUE mCouchbase)
8604
9151
  rb_define_method(cBackend, "document_remove", VALUE_FUNC(cb_Backend_document_remove), 5);
8605
9152
  rb_define_method(cBackend, "document_remove_multi", VALUE_FUNC(cb_Backend_document_remove_multi), 5);
8606
9153
  rb_define_method(cBackend, "document_lookup_in", VALUE_FUNC(cb_Backend_document_lookup_in), 6);
9154
+ rb_define_method(cBackend, "document_lookup_in_any_replica", VALUE_FUNC(cb_Backend_document_lookup_in_any_replica), 6);
9155
+ rb_define_method(cBackend, "document_lookup_in_all_replicas", VALUE_FUNC(cb_Backend_document_lookup_in_all_replicas), 6);
8607
9156
  rb_define_method(cBackend, "document_mutate_in", VALUE_FUNC(cb_Backend_document_mutate_in), 6);
9157
+ rb_define_method(cBackend, "document_scan_create", VALUE_FUNC(cb_Backend_document_scan_create), 5);
8608
9158
  rb_define_method(cBackend, "document_query", VALUE_FUNC(cb_Backend_document_query), 2);
8609
9159
  rb_define_method(cBackend, "document_touch", VALUE_FUNC(cb_Backend_document_touch), 6);
8610
9160
  rb_define_method(cBackend, "document_exists", VALUE_FUNC(cb_Backend_document_exists), 5);
@@ -8706,6 +9256,10 @@ init_backend(VALUE mCouchbase)
8706
9256
  rb_define_singleton_method(cBackend, "query_escape", VALUE_FUNC(cb_Backend_query_escape), 1);
8707
9257
  rb_define_singleton_method(cBackend, "path_escape", VALUE_FUNC(cb_Backend_path_escape), 1);
8708
9258
  rb_define_singleton_method(cBackend, "form_encode", VALUE_FUNC(cb_Backend_form_encode), 1);
9259
+ rb_define_singleton_method(cBackend,
9260
+ "enable_protocol_logger_to_save_network_traffic_to_file",
9261
+ VALUE_FUNC(cb_Backend_enable_protocol_logger_to_save_network_traffic_to_file),
9262
+ 1);
8709
9263
  }
8710
9264
 
8711
9265
  void