couchbase 3.0.0.beta.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +227 -0
  3. data/.rubocop_todo.yml +47 -0
  4. data/CONTRIBUTING.md +110 -0
  5. data/Gemfile +4 -0
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/couchbase.gemspec +40 -39
  9. data/examples/analytics.rb +123 -108
  10. data/examples/auth.rb +33 -0
  11. data/examples/crud.rb +16 -2
  12. data/examples/managing_analytics_indexes.rb +18 -4
  13. data/examples/managing_buckets.rb +17 -3
  14. data/examples/managing_collections.rb +22 -9
  15. data/examples/managing_query_indexes.rb +38 -18
  16. data/examples/managing_search_indexes.rb +21 -6
  17. data/examples/managing_view_indexes.rb +18 -4
  18. data/examples/query.rb +17 -3
  19. data/examples/query_with_consistency.rb +30 -20
  20. data/examples/search.rb +116 -101
  21. data/examples/search_with_consistency.rb +43 -30
  22. data/examples/subdocument.rb +42 -30
  23. data/examples/view.rb +19 -10
  24. data/ext/CMakeLists.txt +40 -2
  25. data/ext/build_version.hxx.in +1 -1
  26. data/ext/couchbase/bucket.hxx +190 -38
  27. data/ext/couchbase/cluster.hxx +22 -4
  28. data/ext/couchbase/configuration.hxx +14 -14
  29. data/ext/couchbase/couchbase.cxx +108 -12
  30. data/ext/couchbase/error_map.hxx +202 -2
  31. data/ext/couchbase/errors.hxx +8 -2
  32. data/ext/couchbase/io/dns_client.hxx +6 -6
  33. data/ext/couchbase/io/http_command.hxx +2 -2
  34. data/ext/couchbase/io/http_session.hxx +7 -11
  35. data/ext/couchbase/io/http_session_manager.hxx +3 -3
  36. data/ext/couchbase/io/mcbp_command.hxx +101 -44
  37. data/ext/couchbase/io/mcbp_session.hxx +144 -49
  38. data/ext/couchbase/io/retry_action.hxx +30 -0
  39. data/ext/couchbase/io/retry_context.hxx +39 -0
  40. data/ext/couchbase/io/retry_orchestrator.hxx +96 -0
  41. data/ext/couchbase/io/retry_reason.hxx +235 -0
  42. data/ext/couchbase/io/retry_strategy.hxx +156 -0
  43. data/ext/couchbase/operations/document_decrement.hxx +2 -0
  44. data/ext/couchbase/operations/document_exists.hxx +2 -0
  45. data/ext/couchbase/operations/document_get.hxx +2 -0
  46. data/ext/couchbase/operations/document_get_and_lock.hxx +2 -0
  47. data/ext/couchbase/operations/document_get_and_touch.hxx +2 -0
  48. data/ext/couchbase/operations/document_get_projected.hxx +2 -0
  49. data/ext/couchbase/operations/document_increment.hxx +2 -0
  50. data/ext/couchbase/operations/document_insert.hxx +2 -0
  51. data/ext/couchbase/operations/document_lookup_in.hxx +2 -0
  52. data/ext/couchbase/operations/document_mutate_in.hxx +3 -0
  53. data/ext/couchbase/operations/document_query.hxx +10 -0
  54. data/ext/couchbase/operations/document_remove.hxx +2 -0
  55. data/ext/couchbase/operations/document_replace.hxx +2 -0
  56. data/ext/couchbase/operations/document_search.hxx +8 -3
  57. data/ext/couchbase/operations/document_touch.hxx +2 -0
  58. data/ext/couchbase/operations/document_unlock.hxx +2 -0
  59. data/ext/couchbase/operations/document_upsert.hxx +2 -0
  60. data/ext/couchbase/operations/query_index_create.hxx +14 -4
  61. data/ext/couchbase/operations/query_index_drop.hxx +12 -2
  62. data/ext/couchbase/operations/query_index_get_all.hxx +11 -2
  63. data/ext/couchbase/origin.hxx +47 -17
  64. data/ext/couchbase/platform/backtrace.c +189 -0
  65. data/ext/couchbase/platform/backtrace.h +54 -0
  66. data/ext/couchbase/platform/terminate_handler.cc +122 -0
  67. data/ext/couchbase/platform/terminate_handler.h +36 -0
  68. data/ext/couchbase/protocol/cmd_get_cluster_config.hxx +6 -1
  69. data/ext/couchbase/protocol/status.hxx +14 -4
  70. data/ext/couchbase/version.hxx +2 -2
  71. data/ext/extconf.rb +39 -36
  72. data/ext/test/main.cxx +64 -16
  73. data/lib/couchbase.rb +0 -1
  74. data/lib/couchbase/analytics_options.rb +2 -4
  75. data/lib/couchbase/authenticator.rb +14 -0
  76. data/lib/couchbase/binary_collection.rb +9 -9
  77. data/lib/couchbase/binary_collection_options.rb +8 -6
  78. data/lib/couchbase/bucket.rb +18 -18
  79. data/lib/couchbase/cluster.rb +121 -90
  80. data/lib/couchbase/collection.rb +36 -38
  81. data/lib/couchbase/collection_options.rb +31 -17
  82. data/lib/couchbase/common_options.rb +1 -1
  83. data/lib/couchbase/datastructures/couchbase_list.rb +16 -16
  84. data/lib/couchbase/datastructures/couchbase_map.rb +18 -18
  85. data/lib/couchbase/datastructures/couchbase_queue.rb +13 -13
  86. data/lib/couchbase/datastructures/couchbase_set.rb +8 -7
  87. data/lib/couchbase/errors.rb +10 -3
  88. data/lib/couchbase/json_transcoder.rb +2 -2
  89. data/lib/couchbase/management/analytics_index_manager.rb +37 -37
  90. data/lib/couchbase/management/bucket_manager.rb +25 -25
  91. data/lib/couchbase/management/collection_manager.rb +3 -3
  92. data/lib/couchbase/management/query_index_manager.rb +59 -14
  93. data/lib/couchbase/management/search_index_manager.rb +15 -12
  94. data/lib/couchbase/management/user_manager.rb +1 -1
  95. data/lib/couchbase/management/view_index_manager.rb +11 -5
  96. data/lib/couchbase/mutation_state.rb +12 -0
  97. data/lib/couchbase/query_options.rb +23 -9
  98. data/lib/couchbase/scope.rb +61 -1
  99. data/lib/couchbase/search_options.rb +40 -27
  100. data/lib/couchbase/subdoc.rb +31 -28
  101. data/lib/couchbase/version.rb +2 -2
  102. data/lib/couchbase/view_options.rb +0 -1
  103. metadata +22 -9
@@ -53,7 +53,8 @@ class cluster
53
53
  {
54
54
  origin_ = origin;
55
55
  if (origin_.options().enable_dns_srv) {
56
- return do_dns_srv(std::forward<Handler>(handler));
56
+ return asio::post(asio::bind_executor(
57
+ ctx_, [this, handler = std::forward<Handler>(handler)]() mutable { return do_dns_srv(std::forward<Handler>(handler)); }));
57
58
  }
58
59
  do_open(std::forward<Handler>(handler));
59
60
  }
@@ -63,7 +64,7 @@ class cluster
63
64
  {
64
65
  asio::post(asio::bind_executor(ctx_, [this, handler = std::forward<Handler>(handler)]() {
65
66
  if (session_) {
66
- session_->stop();
67
+ session_->stop(io::retry_reason::do_not_retry);
67
68
  }
68
69
  for (auto& bucket : buckets_) {
69
70
  bucket.second->close();
@@ -103,7 +104,7 @@ class cluster
103
104
  template<class Request, class Handler>
104
105
  void execute_http(Request request, Handler&& handler)
105
106
  {
106
- auto session = session_manager_->check_out(Request::type, origin_.get_username(), origin_.get_password());
107
+ auto session = session_manager_->check_out(Request::type, origin_.credentials());
107
108
  if (!session) {
108
109
  return handler(operations::make_response(std::make_error_code(error::common_errc::service_not_available), request, {}));
109
110
  }
@@ -157,9 +158,11 @@ class cluster
157
158
  tls_.set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3);
158
159
  if (!origin_.options().trust_certificate.empty()) {
159
160
  std::error_code ec{};
161
+ spdlog::debug(R"([{}]: use TLS certificate chain: "{}")", id_, origin_.options().trust_certificate);
160
162
  tls_.use_certificate_chain_file(origin_.options().trust_certificate, ec);
161
163
  if (ec) {
162
- spdlog::error("unable to load certificate chain \"{}\": {}", origin_.options().trust_certificate, ec.message());
164
+ spdlog::error(
165
+ "[{}]: unable to load certificate chain \"{}\": {}", id_, origin_.options().trust_certificate, ec.message());
163
166
  return handler(ec);
164
167
  }
165
168
  }
@@ -172,6 +175,21 @@ class cluster
172
175
  "(https://wiki.wireshark.org/TLS). DO NOT USE THIS BUILD IN PRODUCTION",
173
176
  TLS_KEY_LOG_FILE);
174
177
  #endif
178
+ if (origin_.credentials().uses_certificate()) {
179
+ std::error_code ec{};
180
+ spdlog::debug(R"([{}]: use TLS certificate: "{}")", id_, origin_.certificate_path());
181
+ tls_.use_certificate_file(origin_.certificate_path(), asio::ssl::context::file_format::pem, ec);
182
+ if (ec) {
183
+ spdlog::error("[{}]: unable to load certificate \"{}\": {}", id_, origin_.certificate_path(), ec.message());
184
+ return handler(ec);
185
+ }
186
+ spdlog::debug(R"([{}]: use TLS private key: "{}")", id_, origin_.key_path());
187
+ tls_.use_private_key_file(origin_.key_path(), asio::ssl::context::file_format::pem, ec);
188
+ if (ec) {
189
+ spdlog::error("[{}]: unable to load private key \"{}\": {}", id_, origin_.key_path(), ec.message());
190
+ return handler(ec);
191
+ }
192
+ }
175
193
  session_ = std::make_shared<io::mcbp_session>(id_, ctx_, tls_, origin_);
176
194
  } else {
177
195
  session_ = std::make_shared<io::mcbp_session>(id_, ctx_, origin_);
@@ -30,25 +30,25 @@ namespace couchbase
30
30
  {
31
31
  struct configuration {
32
32
  struct port_map {
33
- std::optional<std::uint16_t> key_value;
34
- std::optional<std::uint16_t> management;
35
- std::optional<std::uint16_t> analytics;
36
- std::optional<std::uint16_t> search;
37
- std::optional<std::uint16_t> views;
38
- std::optional<std::uint16_t> query;
33
+ std::optional<std::uint16_t> key_value{};
34
+ std::optional<std::uint16_t> management{};
35
+ std::optional<std::uint16_t> analytics{};
36
+ std::optional<std::uint16_t> search{};
37
+ std::optional<std::uint16_t> views{};
38
+ std::optional<std::uint16_t> query{};
39
39
  };
40
40
  struct alternate_address {
41
- std::string name;
42
- std::string hostname;
41
+ std::string name{};
42
+ std::string hostname{};
43
43
  port_map services_plain{};
44
44
  port_map services_tls{};
45
45
  };
46
46
  struct node {
47
47
  bool this_node{ false };
48
- size_t index;
49
- std::string hostname;
50
- port_map services_plain;
51
- port_map services_tls;
48
+ size_t index{};
49
+ std::string hostname{};
50
+ port_map services_plain{};
51
+ port_map services_tls{};
52
52
  std::map<std::string, alternate_address> alt{};
53
53
 
54
54
  [[nodiscard]] std::uint16_t port_or(service_type type, bool is_tls, std::uint16_t default_value) const
@@ -211,14 +211,14 @@ struct configuration {
211
211
  throw std::runtime_error("no nodes marked as this_node");
212
212
  }
213
213
 
214
- std::pair<uint16_t, size_t> map_key(const std::string& key)
214
+ std::pair<std::uint16_t, std::int16_t> map_key(const std::string& key)
215
215
  {
216
216
  if (!vbmap.has_value()) {
217
217
  throw std::runtime_error("cannot map key: partition map is not available");
218
218
  }
219
219
  uint32_t crc = utils::hash_crc32(key.data(), key.size());
220
220
  uint16_t vbucket = uint16_t(crc % vbmap->size());
221
- return std::make_pair(vbucket, static_cast<std::size_t>(vbmap->at(vbucket)[0]));
221
+ return std::make_pair(vbucket, vbmap->at(vbucket)[0]);
222
222
  }
223
223
  };
224
224
 
@@ -28,6 +28,8 @@
28
28
  #include <snappy.h>
29
29
 
30
30
  #include <version.hxx>
31
+ #include <platform/terminate_handler.h>
32
+
31
33
  #include <cluster.hxx>
32
34
  #include <operations.hxx>
33
35
 
@@ -165,6 +167,7 @@ cb_Backend_allocate(VALUE klass)
165
167
  }
166
168
 
167
169
  static VALUE eCouchbaseError;
170
+ static VALUE eTimeout;
168
171
  static VALUE eAmbiguousTimeout;
169
172
  static VALUE eAuthenticationFailure;
170
173
  static VALUE eBucketExists;
@@ -197,6 +200,7 @@ static VALUE eGroupNotFound;
197
200
  static VALUE eIndexExists;
198
201
  static VALUE eIndexFailure;
199
202
  static VALUE eIndexNotFound;
203
+ static VALUE eIndexNotReady;
200
204
  static VALUE eInternalServerFailure;
201
205
  static VALUE eInvalidArgument;
202
206
  static VALUE eJobQueueFull;
@@ -235,7 +239,9 @@ init_exceptions(VALUE mCouchbase)
235
239
  VALUE mError = rb_define_module_under(mCouchbase, "Error");
236
240
  eCouchbaseError = rb_define_class_under(mError, "CouchbaseError", rb_eStandardError);
237
241
 
238
- eAmbiguousTimeout = rb_define_class_under(mError, "AmbiguousTimeout", eCouchbaseError);
242
+ eTimeout = rb_define_class_under(mError, "Timeout", eCouchbaseError);
243
+
244
+ eAmbiguousTimeout = rb_define_class_under(mError, "AmbiguousTimeout", eTimeout);
239
245
  eAuthenticationFailure = rb_define_class_under(mError, "AuthenticationFailure", eCouchbaseError);
240
246
  eBucketExists = rb_define_class_under(mError, "BucketExists", eCouchbaseError);
241
247
  eBucketNotFlushable = rb_define_class_under(mError, "BucketNotFlushable", eCouchbaseError);
@@ -267,6 +273,7 @@ init_exceptions(VALUE mCouchbase)
267
273
  eIndexExists = rb_define_class_under(mError, "IndexExists", eCouchbaseError);
268
274
  eIndexFailure = rb_define_class_under(mError, "IndexFailure", eCouchbaseError);
269
275
  eIndexNotFound = rb_define_class_under(mError, "IndexNotFound", eCouchbaseError);
276
+ eIndexNotReady = rb_define_class_under(mError, "IndexNotReady", eCouchbaseError);
270
277
  eInternalServerFailure = rb_define_class_under(mError, "InternalServerFailure", eCouchbaseError);
271
278
  eInvalidArgument = rb_define_class_under(mError, "InvalidArgument", rb_eArgError);
272
279
  eJobQueueFull = rb_define_class_under(mError, "JobQueueFull", eCouchbaseError);
@@ -286,7 +293,7 @@ init_exceptions(VALUE mCouchbase)
286
293
  eScopeNotFound = rb_define_class_under(mError, "ScopeNotFound", eCouchbaseError);
287
294
  eServiceNotAvailable = rb_define_class_under(mError, "ServiceNotAvailable", eCouchbaseError);
288
295
  eTemporaryFailure = rb_define_class_under(mError, "TemporaryFailure", eCouchbaseError);
289
- eUnambiguousTimeout = rb_define_class_under(mError, "UnambiguousTimeout", eCouchbaseError);
296
+ eUnambiguousTimeout = rb_define_class_under(mError, "UnambiguousTimeout", eTimeout);
290
297
  eUnsupportedOperation = rb_define_class_under(mError, "UnsupportedOperation", eCouchbaseError);
291
298
  eUserNotFound = rb_define_class_under(mError, "UserNotFound", eCouchbaseError);
292
299
  eUserExists = rb_define_class_under(mError, "UserExists", eCouchbaseError);
@@ -450,6 +457,11 @@ cb__map_error_code(std::error_code ec, const std::string& message)
450
457
  case couchbase::error::query_errc::prepared_statement_failure:
451
458
  return rb_exc_new_cstr(ePreparedStatementFailure, fmt::format("{}: {}", message, ec.message()).c_str());
452
459
  }
460
+ } else if (ec.category() == couchbase::error::detail::get_search_category()) {
461
+ switch (static_cast<couchbase::error::search_errc>(ec.value())) {
462
+ case couchbase::error::search_errc::index_not_ready:
463
+ return rb_exc_new_cstr(eIndexNotReady, fmt::format("{}: {}", message, ec.message()).c_str());
464
+ }
453
465
  } else if (ec.category() == couchbase::error::detail::get_view_category()) {
454
466
  switch (static_cast<couchbase::error::view_errc>(ec.value())) {
455
467
  case couchbase::error::view_errc::view_not_found:
@@ -510,7 +522,7 @@ cb__map_error_code(std::error_code ec, const std::string& message)
510
522
  }
511
523
 
512
524
  static VALUE
513
- cb_Backend_open(VALUE self, VALUE connection_string, VALUE username, VALUE password, VALUE options)
525
+ cb_Backend_open(VALUE self, VALUE connection_string, VALUE credentials, VALUE options)
514
526
  {
515
527
  cb_backend_data* backend = nullptr;
516
528
  TypedData_Get_Struct(self, cb_backend_data, &cb_backend_type, backend);
@@ -519,26 +531,52 @@ cb_Backend_open(VALUE self, VALUE connection_string, VALUE username, VALUE passw
519
531
  rb_raise(rb_eArgError, "Cluster has been closed already");
520
532
  }
521
533
  Check_Type(connection_string, T_STRING);
522
- Check_Type(username, T_STRING);
523
- Check_Type(password, T_STRING);
534
+ Check_Type(credentials, T_HASH);
535
+
536
+ VALUE username = Qnil;
537
+ VALUE password = Qnil;
538
+
539
+ VALUE certificate_path = rb_hash_aref(credentials, rb_id2sym(rb_intern("certificate_path")));
540
+ VALUE key_path = rb_hash_aref(credentials, rb_id2sym(rb_intern("key_path")));
541
+ if (NIL_P(certificate_path) || NIL_P(key_path)) {
542
+ username = rb_hash_aref(credentials, rb_id2sym(rb_intern("username")));
543
+ password = rb_hash_aref(credentials, rb_id2sym(rb_intern("password")));
544
+ Check_Type(username, T_STRING);
545
+ Check_Type(password, T_STRING);
546
+ } else {
547
+ Check_Type(certificate_path, T_STRING);
548
+ Check_Type(key_path, T_STRING);
549
+ }
524
550
  if (!NIL_P(options)) {
525
551
  Check_Type(options, T_HASH);
526
552
  }
527
553
 
528
554
  VALUE exc = Qnil;
529
- {
555
+ do {
530
556
  std::string input(RSTRING_PTR(connection_string), static_cast<size_t>(RSTRING_LEN(connection_string)));
531
557
  auto connstr = couchbase::utils::parse_connection_string(input);
532
- std::string user(RSTRING_PTR(username), static_cast<size_t>(RSTRING_LEN(username)));
533
- std::string pass(RSTRING_PTR(password), static_cast<size_t>(RSTRING_LEN(password)));
534
- couchbase::origin origin(user, pass, std::move(connstr));
558
+ couchbase::cluster_credentials auth{};
559
+ if (NIL_P(certificate_path) || NIL_P(key_path)) {
560
+ auth.username.assign(RSTRING_PTR(username), static_cast<size_t>(RSTRING_LEN(username)));
561
+ auth.password.assign(RSTRING_PTR(password), static_cast<size_t>(RSTRING_LEN(password)));
562
+ } else {
563
+ if (!connstr.tls) {
564
+ exc = rb_exc_new_cstr(
565
+ eInvalidArgument,
566
+ fmt::format("Certificate authenticator requires TLS connection, check the schema of the connection string").c_str());
567
+ break;
568
+ }
569
+ auth.certificate_path.assign(RSTRING_PTR(certificate_path), static_cast<size_t>(RSTRING_LEN(certificate_path)));
570
+ auth.key_path.assign(RSTRING_PTR(key_path), static_cast<size_t>(RSTRING_LEN(key_path)));
571
+ }
572
+ couchbase::origin origin(auth, std::move(connstr));
535
573
  auto barrier = std::make_shared<std::promise<std::error_code>>();
536
574
  auto f = barrier->get_future();
537
575
  backend->cluster->open(origin, [barrier](std::error_code ec) mutable { barrier->set_value(ec); });
538
576
  if (auto ec = f.get()) {
539
577
  exc = cb__map_error_code(ec, fmt::format("unable open cluster at {}", origin.next_address().first));
540
578
  }
541
- }
579
+ } while (false);
542
580
  if (!NIL_P(exc)) {
543
581
  rb_exc_raise(exc);
544
582
  }
@@ -1958,6 +1996,22 @@ cb_Backend_document_query(VALUE self, VALUE statement, VALUE options)
1958
1996
  if (!NIL_P(pipeline_batch)) {
1959
1997
  req.pipeline_batch = NUM2ULONG(pipeline_batch);
1960
1998
  }
1999
+ VALUE scope_qualifier = rb_hash_aref(options, rb_id2sym(rb_intern("scope_qualifier")));
2000
+ if (!NIL_P(scope_qualifier) && TYPE(scope_qualifier) == T_STRING) {
2001
+ req.scope_qualifier.emplace(std::string(RSTRING_PTR(scope_qualifier), static_cast<std::size_t>(RSTRING_LEN(scope_qualifier))));
2002
+ } else {
2003
+ VALUE scope_name = rb_hash_aref(options, rb_id2sym(rb_intern("scope_name")));
2004
+ if (!NIL_P(scope_name) && TYPE(scope_name) == T_STRING) {
2005
+ req.scope_name.emplace(std::string(RSTRING_PTR(scope_name), static_cast<std::size_t>(RSTRING_LEN(scope_name))));
2006
+ VALUE bucket_name = rb_hash_aref(options, rb_id2sym(rb_intern("bucket_name")));
2007
+ if (NIL_P(bucket_name)) {
2008
+ exc = rb_exc_new_cstr(
2009
+ eInvalidArgument, fmt::format("bucket must be specified for query in scope \"{}\"", req.scope_name.value()).c_str());
2010
+ break;
2011
+ }
2012
+ req.bucket_name.emplace(std::string(RSTRING_PTR(bucket_name), static_cast<std::size_t>(RSTRING_LEN(bucket_name))));
2013
+ }
2014
+ }
1961
2015
  VALUE profile = rb_hash_aref(options, rb_id2sym(rb_intern("profile")));
1962
2016
  if (!NIL_P(profile)) {
1963
2017
  Check_Type(profile, T_SYMBOL);
@@ -2776,6 +2830,14 @@ cb_Backend_query_index_get_all(VALUE self, VALUE bucket_name, VALUE timeout)
2776
2830
  rb_ary_push(index_key, rb_str_new(key.data(), static_cast<long>(key.size())));
2777
2831
  }
2778
2832
  rb_hash_aset(index, rb_id2sym(rb_intern("index_key")), index_key);
2833
+ if (idx.scope_id) {
2834
+ rb_hash_aset(
2835
+ index, rb_id2sym(rb_intern("scope_id")), rb_str_new(idx.scope_id->data(), static_cast<long>(idx.scope_id->size())));
2836
+ }
2837
+ if (idx.bucket_id) {
2838
+ rb_hash_aset(
2839
+ index, rb_id2sym(rb_intern("bucket_id")), rb_str_new(idx.bucket_id->data(), static_cast<long>(idx.bucket_id->size())));
2840
+ }
2779
2841
  if (idx.condition) {
2780
2842
  rb_hash_aset(
2781
2843
  index, rb_id2sym(rb_intern("condition")), rb_str_new(idx.condition->data(), static_cast<long>(idx.condition->size())));
@@ -2840,6 +2902,14 @@ cb_Backend_query_index_create(VALUE self, VALUE bucket_name, VALUE index_name, V
2840
2902
  if (!NIL_P(condition)) {
2841
2903
  req.condition.emplace(std::string(RSTRING_PTR(condition), static_cast<std::size_t>(RSTRING_LEN(condition))));
2842
2904
  } /* else use backend default */
2905
+ VALUE scope_name = rb_hash_aref(options, rb_id2sym(rb_intern("scope_name")));
2906
+ if (TYPE(scope_name) == T_STRING) {
2907
+ req.scope_name.assign(RSTRING_PTR(scope_name), static_cast<size_t>(RSTRING_LEN(scope_name)));
2908
+ }
2909
+ VALUE collection_name = rb_hash_aref(options, rb_id2sym(rb_intern("collection_name")));
2910
+ if (TYPE(scope_name) == T_STRING) {
2911
+ req.collection_name.assign(RSTRING_PTR(collection_name), static_cast<size_t>(RSTRING_LEN(collection_name)));
2912
+ }
2843
2913
  }
2844
2914
 
2845
2915
  auto barrier = std::make_shared<std::promise<couchbase::operations::query_index_create_response>>();
@@ -2907,6 +2977,14 @@ cb_Backend_query_index_drop(VALUE self, VALUE bucket_name, VALUE index_name, VAL
2907
2977
  } else if (ignore_if_does_not_exist == Qfalse) {
2908
2978
  req.ignore_if_does_not_exist = false;
2909
2979
  } /* else use backend default */
2980
+ VALUE scope_name = rb_hash_aref(options, rb_id2sym(rb_intern("scope_name")));
2981
+ if (TYPE(scope_name) == T_STRING) {
2982
+ req.scope_name.assign(RSTRING_PTR(scope_name), static_cast<size_t>(RSTRING_LEN(scope_name)));
2983
+ }
2984
+ VALUE collection_name = rb_hash_aref(options, rb_id2sym(rb_intern("collection_name")));
2985
+ if (TYPE(scope_name) == T_STRING) {
2986
+ req.collection_name.assign(RSTRING_PTR(collection_name), static_cast<size_t>(RSTRING_LEN(collection_name)));
2987
+ }
2910
2988
  }
2911
2989
 
2912
2990
  auto barrier = std::make_shared<std::promise<couchbase::operations::query_index_drop_response>>();
@@ -2990,6 +3068,14 @@ cb_Backend_query_index_create_primary(VALUE self, VALUE bucket_name, VALUE optio
2990
3068
  if (!NIL_P(index_name)) {
2991
3069
  req.index_name.assign(RSTRING_PTR(index_name), static_cast<size_t>(RSTRING_LEN(index_name)));
2992
3070
  } /* else use backend default */
3071
+ VALUE scope_name = rb_hash_aref(options, rb_id2sym(rb_intern("scope_name")));
3072
+ if (TYPE(scope_name) == T_STRING) {
3073
+ req.scope_name.assign(RSTRING_PTR(scope_name), static_cast<size_t>(RSTRING_LEN(scope_name)));
3074
+ }
3075
+ VALUE collection_name = rb_hash_aref(options, rb_id2sym(rb_intern("collection_name")));
3076
+ if (TYPE(scope_name) == T_STRING) {
3077
+ req.collection_name.assign(RSTRING_PTR(collection_name), static_cast<size_t>(RSTRING_LEN(collection_name)));
3078
+ }
2993
3079
  }
2994
3080
 
2995
3081
  auto barrier = std::make_shared<std::promise<couchbase::operations::query_index_create_response>>();
@@ -3061,6 +3147,14 @@ cb_Backend_query_index_drop_primary(VALUE self, VALUE bucket_name, VALUE options
3061
3147
  req.is_primary = false;
3062
3148
  req.bucket_name.assign(RSTRING_PTR(index_name), static_cast<size_t>(RSTRING_LEN(index_name)));
3063
3149
  }
3150
+ VALUE scope_name = rb_hash_aref(options, rb_id2sym(rb_intern("scope_name")));
3151
+ if (TYPE(scope_name) == T_STRING) {
3152
+ req.scope_name.assign(RSTRING_PTR(scope_name), static_cast<size_t>(RSTRING_LEN(scope_name)));
3153
+ }
3154
+ VALUE collection_name = rb_hash_aref(options, rb_id2sym(rb_intern("collection_name")));
3155
+ if (TYPE(scope_name) == T_STRING) {
3156
+ req.collection_name.assign(RSTRING_PTR(collection_name), static_cast<size_t>(RSTRING_LEN(collection_name)));
3157
+ }
3064
3158
  }
3065
3159
 
3066
3160
  auto barrier = std::make_shared<std::promise<couchbase::operations::query_index_drop_response>>();
@@ -5347,7 +5441,7 @@ init_backend(VALUE mCouchbase)
5347
5441
  {
5348
5442
  VALUE cBackend = rb_define_class_under(mCouchbase, "Backend", rb_cBasicObject);
5349
5443
  rb_define_alloc_func(cBackend, cb_Backend_allocate);
5350
- rb_define_method(cBackend, "open", VALUE_FUNC(cb_Backend_open), 4);
5444
+ rb_define_method(cBackend, "open", VALUE_FUNC(cb_Backend_open), 3);
5351
5445
  rb_define_method(cBackend, "close", VALUE_FUNC(cb_Backend_close), 0);
5352
5446
  rb_define_method(cBackend, "open_bucket", VALUE_FUNC(cb_Backend_open_bucket), 2);
5353
5447
 
@@ -5435,11 +5529,13 @@ init_logger()
5435
5529
 
5436
5530
  auto env_val = spdlog::details::os::getenv("COUCHBASE_BACKEND_LOG_LEVEL");
5437
5531
  if (env_val.empty()) {
5438
- spdlog::set_level(spdlog::level::critical);
5532
+ spdlog::set_level(spdlog::level::warn);
5439
5533
  } else {
5440
5534
  auto levels = spdlog::cfg::helpers::extract_levels(env_val);
5441
5535
  spdlog::details::registry::instance().update_levels(std::move(levels));
5442
5536
  }
5537
+
5538
+ couchbase::platform::install_backtrace_terminate_handler();
5443
5539
  }
5444
5540
 
5445
5541
  extern "C" {
@@ -20,12 +20,112 @@
20
20
  namespace couchbase
21
21
  {
22
22
  struct error_map {
23
+ enum class attribute {
24
+ /**
25
+ * The operation was successful for those situations where the error code is indicating successful (i.e. subdoc operations carried
26
+ * out on a deleted document)
27
+ */
28
+ success,
29
+
30
+ /**
31
+ * This attribute means that the error is related to a constraint failure regarding the item itself, i.e. the item does not exist,
32
+ * already exists, or its current value makes the current operation impossible. Retrying the operation when the item's value or
33
+ * status has changed may succeed.
34
+ */
35
+ item_only,
36
+
37
+ /**
38
+ * This attribute means that a user's input was invalid because it violates the semantics of the operation, or exceeds some
39
+ * predefined limit.
40
+ */
41
+ invalid_input,
42
+
43
+ /**
44
+ * The client's cluster map may be outdated and requires updating. The client should obtain a newer configuration.
45
+ */
46
+ fetch_config,
47
+
48
+ /**
49
+ * The current connection is no longer valid. The client must reconnect to the server. Note that the presence of other attributes
50
+ * may indicate an alternate remedy to fixing the connection without a disconnect, but without special remedial action a disconnect
51
+ * is needed.
52
+ */
53
+ conn_state_invalidated,
54
+
55
+ /**
56
+ * The operation failed because the client failed to authenticate or is not authorized to perform this operation. Note that this
57
+ * error in itself does not mean the connection is invalid, unless conn-state-invalidated is also present.
58
+ */
59
+ auth,
60
+
61
+ /**
62
+ * This error code must be handled specially. If it is not handled, the connection must be dropped.
63
+ */
64
+ special_handling,
65
+
66
+ /**
67
+ * The operation is not supported, possibly because the of server version, bucket type, or current user.
68
+ */
69
+ support,
70
+
71
+ /**
72
+ * This error is transient. Note that this does not mean the error is retriable.
73
+ */
74
+ temp,
75
+
76
+ /**
77
+ * This is an internal error in the server.
78
+ */
79
+ internal,
80
+
81
+ /**
82
+ * The operation may be retried immediately.
83
+ */
84
+ retry_now,
85
+
86
+ /**
87
+ * The operation may be retried after some time.
88
+ */
89
+ retry_later,
90
+
91
+ /**
92
+ * The error is related to the subdocument subsystem.
93
+ */
94
+ subdoc,
95
+
96
+ /**
97
+ * The error is related to the DCP subsystem.
98
+ */
99
+ dcp,
100
+
101
+ /**
102
+ * Use retry specifications from the server.
103
+ */
104
+ auto_retry,
105
+
106
+ /**
107
+ * This attribute specifies that the requested item is currently locked.
108
+ */
109
+ item_locked,
110
+
111
+ /**
112
+ * This attribute means that the error is related to operating on a soft-deleted document.
113
+ */
114
+ item_deleted,
115
+ };
116
+
23
117
  struct error_info {
24
118
  std::uint16_t code;
25
119
  std::string name;
26
120
  std::string description;
27
- std::set<std::string> attributes;
121
+ std::set<attribute> attributes;
122
+
123
+ bool has_retry_attribute()
124
+ {
125
+ return attributes.find(attribute::retry_now) != attributes.end() || attributes.find(attribute::retry_later) != attributes.end();
126
+ }
28
127
  };
128
+
29
129
  uuid::uuid_t id;
30
130
  uint16_t version;
31
131
  uint16_t revision;
@@ -51,7 +151,44 @@ struct traits<couchbase::error_map> {
51
151
  ei.name = info.at("name").get_string();
52
152
  ei.description = info.at("desc").get_string();
53
153
  for (const auto& a : info.at("attrs").get_array()) {
54
- ei.attributes.insert(a.get_string());
154
+ const std::string& attr_val = a.get_string();
155
+ if (attr_val == "success") {
156
+ ei.attributes.insert(couchbase::error_map::attribute::success);
157
+ } else if (attr_val == "item-only") {
158
+ ei.attributes.insert(couchbase::error_map::attribute::item_only);
159
+ } else if (attr_val == "invalid-input") {
160
+ ei.attributes.insert(couchbase::error_map::attribute::invalid_input);
161
+ } else if (attr_val == "fetch-config") {
162
+ ei.attributes.insert(couchbase::error_map::attribute::fetch_config);
163
+ } else if (attr_val == "conn-state-invalidated") {
164
+ ei.attributes.insert(couchbase::error_map::attribute::conn_state_invalidated);
165
+ } else if (attr_val == "auth") {
166
+ ei.attributes.insert(couchbase::error_map::attribute::auth);
167
+ } else if (attr_val == "special-handling") {
168
+ ei.attributes.insert(couchbase::error_map::attribute::special_handling);
169
+ } else if (attr_val == "support") {
170
+ ei.attributes.insert(couchbase::error_map::attribute::support);
171
+ } else if (attr_val == "temp") {
172
+ ei.attributes.insert(couchbase::error_map::attribute::temp);
173
+ } else if (attr_val == "internal") {
174
+ ei.attributes.insert(couchbase::error_map::attribute::internal);
175
+ } else if (attr_val == "retry-now") {
176
+ ei.attributes.insert(couchbase::error_map::attribute::retry_now);
177
+ } else if (attr_val == "retry-later") {
178
+ ei.attributes.insert(couchbase::error_map::attribute::retry_later);
179
+ } else if (attr_val == "subdoc") {
180
+ ei.attributes.insert(couchbase::error_map::attribute::subdoc);
181
+ } else if (attr_val == "dcp") {
182
+ ei.attributes.insert(couchbase::error_map::attribute::dcp);
183
+ } else if (attr_val == "auto-retry") {
184
+ ei.attributes.insert(couchbase::error_map::attribute::auto_retry);
185
+ } else if (attr_val == "item-locked") {
186
+ ei.attributes.insert(couchbase::error_map::attribute::item_locked);
187
+ } else if (attr_val == "item-deleted") {
188
+ ei.attributes.insert(couchbase::error_map::attribute::item_deleted);
189
+ } else {
190
+ spdlog::warn(R"(skipping unknown attribute "{}" in error map for code={} and name="{}")", attr_val, ei.code, ei.name);
191
+ }
55
192
  }
56
193
  result.errors.emplace(ei.code, ei);
57
194
  }
@@ -59,3 +196,66 @@ struct traits<couchbase::error_map> {
59
196
  }
60
197
  };
61
198
  } // namespace tao::json
199
+
200
+ template<>
201
+ struct fmt::formatter<couchbase::error_map::attribute> : formatter<string_view> {
202
+ template<typename FormatContext>
203
+ auto format(couchbase::error_map::attribute attr, FormatContext& ctx)
204
+ {
205
+ string_view name = "unknown";
206
+ switch (attr) {
207
+ case couchbase::error_map::attribute::success:
208
+ name = "success";
209
+ break;
210
+ case couchbase::error_map::attribute::item_only:
211
+ name = "item-only";
212
+ break;
213
+ case couchbase::error_map::attribute::invalid_input:
214
+ name = "invalid-input";
215
+ break;
216
+ case couchbase::error_map::attribute::fetch_config:
217
+ name = "fetch-config";
218
+ break;
219
+ case couchbase::error_map::attribute::conn_state_invalidated:
220
+ name = "conn-state-invalidated";
221
+ break;
222
+ case couchbase::error_map::attribute::auth:
223
+ name = "auth";
224
+ break;
225
+ case couchbase::error_map::attribute::special_handling:
226
+ name = "special-handling";
227
+ break;
228
+ case couchbase::error_map::attribute::support:
229
+ name = "support";
230
+ break;
231
+ case couchbase::error_map::attribute::temp:
232
+ name = "temp";
233
+ break;
234
+ case couchbase::error_map::attribute::internal:
235
+ name = "internal";
236
+ break;
237
+ case couchbase::error_map::attribute::retry_now:
238
+ name = "retry-now";
239
+ break;
240
+ case couchbase::error_map::attribute::retry_later:
241
+ name = "retry-later";
242
+ break;
243
+ case couchbase::error_map::attribute::subdoc:
244
+ name = "subdoc";
245
+ break;
246
+ case couchbase::error_map::attribute::dcp:
247
+ name = "dcp";
248
+ break;
249
+ case couchbase::error_map::attribute::auto_retry:
250
+ name = "auto-retry";
251
+ break;
252
+ case couchbase::error_map::attribute::item_locked:
253
+ name = "item-locked";
254
+ break;
255
+ case couchbase::error_map::attribute::item_deleted:
256
+ name = "item-deleted";
257
+ break;
258
+ }
259
+ return formatter<string_view>::format(name, ctx);
260
+ }
261
+ };