couchbase 3.8.0 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/ext/cache/extconf_include.rb +4 -4
  4. data/ext/cache/mozilla-ca-bundle.crt +3 -575
  5. data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
  6. data/ext/couchbase/CMakeLists.txt +3 -1
  7. data/ext/couchbase/core/error_context/base_error_context.hxx +32 -0
  8. data/ext/couchbase/core/error_context/key_value.cxx +1 -0
  9. data/ext/couchbase/core/error_context/key_value.hxx +23 -0
  10. data/ext/couchbase/core/error_context/key_value_error_context.hxx +35 -0
  11. data/ext/couchbase/core/error_context/subdocument_error_context.hxx +41 -0
  12. data/ext/couchbase/core/impl/binary_collection.cxx +123 -88
  13. data/ext/couchbase/core/impl/collection.cxx +416 -189
  14. data/ext/couchbase/core/impl/error.cxx +29 -4
  15. data/ext/couchbase/core/impl/invoke_with_node_id.hxx +44 -0
  16. data/ext/couchbase/core/impl/node_id.cxx +110 -0
  17. data/ext/couchbase/core/impl/node_id.hxx +40 -0
  18. data/ext/couchbase/core/io/http_session_manager.hxx +97 -57
  19. data/ext/couchbase/core/operations/document_get_all_replicas.hxx +14 -4
  20. data/ext/couchbase/core/operations/document_get_projected.cxx +3 -4
  21. data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +4 -0
  22. data/ext/couchbase/core/operations/document_query.cxx +12 -12
  23. data/ext/couchbase/core/operations/management/collection_create.cxx +11 -11
  24. data/ext/couchbase/core/operations/management/collection_drop.cxx +11 -11
  25. data/ext/couchbase/core/operations/management/collection_update.cxx +11 -11
  26. data/ext/couchbase/core/operations/management/error_utils.cxx +9 -6
  27. data/ext/couchbase/core/operations/management/query_index_create.cxx +5 -4
  28. data/ext/couchbase/core/operations/management/query_index_drop.cxx +7 -6
  29. data/ext/couchbase/core/operations/management/scope_create.cxx +12 -13
  30. data/ext/couchbase/core/operations/management/scope_drop.cxx +8 -9
  31. data/ext/couchbase/core/topology/configuration.cxx +21 -0
  32. data/ext/couchbase/core/topology/configuration.hxx +28 -0
  33. data/ext/couchbase/core/utils/contains_string.cxx +61 -0
  34. data/ext/couchbase/core/utils/contains_string.hxx +26 -0
  35. data/ext/couchbase/couchbase/collection.hxx +73 -0
  36. data/ext/couchbase/couchbase/error.hxx +16 -0
  37. data/ext/couchbase/couchbase/node_id.hxx +123 -0
  38. data/ext/couchbase/couchbase/node_id_for_options.hxx +61 -0
  39. data/ext/couchbase/couchbase/node_ids_options.hxx +62 -0
  40. data/ext/couchbase/couchbase/result.hxx +42 -0
  41. data/ext/rcb_crud.cxx +2 -0
  42. data/ext/rcb_logger.cxx +15 -17
  43. data/lib/couchbase/datastructures/couchbase_list.rb +1 -0
  44. data/lib/couchbase/datastructures/couchbase_map.rb +1 -0
  45. data/lib/couchbase/datastructures/couchbase_queue.rb +1 -0
  46. data/lib/couchbase/datastructures/couchbase_set.rb +1 -0
  47. data/lib/couchbase/errors.rb +1 -1
  48. data/lib/couchbase/json_transcoder.rb +1 -1
  49. data/lib/couchbase/options.rb +2 -2
  50. data/lib/couchbase/protostellar/client.rb +0 -2
  51. data/lib/couchbase/protostellar/cluster.rb +4 -0
  52. data/lib/couchbase/protostellar/generated/admin/analytics/v1/analytics_pb.rb +54 -0
  53. data/lib/couchbase/protostellar/generated/admin/analytics/v1/analytics_services_pb.rb +51 -0
  54. data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_pb.rb +5 -24
  55. data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_services_pb.rb +16 -0
  56. data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_pb.rb +5 -24
  57. data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_services_pb.rb +16 -0
  58. data/lib/couchbase/protostellar/generated/admin/query/v1/query_pb.rb +5 -24
  59. data/lib/couchbase/protostellar/generated/admin/query/v1/query_services_pb.rb +18 -0
  60. data/lib/couchbase/protostellar/generated/admin/search/v1/search_pb.rb +2 -23
  61. data/lib/couchbase/protostellar/generated/admin/search/v1/search_services_pb.rb +23 -0
  62. data/lib/couchbase/protostellar/generated/analytics/v1/analytics_pb.rb +4 -25
  63. data/lib/couchbase/protostellar/generated/analytics/v1/analytics_services_pb.rb +10 -0
  64. data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_pb.rb +6 -25
  65. data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_services_pb.rb +18 -0
  66. data/lib/couchbase/protostellar/generated/internal/xdcr/v1/xdcr_pb.rb +46 -0
  67. data/lib/couchbase/protostellar/generated/internal/xdcr/v1/xdcr_services_pb.rb +56 -0
  68. data/lib/couchbase/protostellar/generated/kv/v1/kv_pb.rb +3 -26
  69. data/lib/couchbase/protostellar/generated/kv/v1/kv_services_pb.rb +47 -0
  70. data/lib/couchbase/protostellar/generated/query/v1/query_pb.rb +4 -26
  71. data/lib/couchbase/protostellar/generated/query/v1/query_services_pb.rb +10 -0
  72. data/lib/couchbase/protostellar/generated/routing/v2/routing_pb.rb +26 -0
  73. data/lib/couchbase/protostellar/generated/routing/v2/routing_services_pb.rb +43 -0
  74. data/lib/couchbase/protostellar/generated/search/v1/search_pb.rb +5 -26
  75. data/lib/couchbase/protostellar/generated/search/v1/search_services_pb.rb +11 -0
  76. data/lib/couchbase/protostellar/generated/transactions/v1/transactions_pb.rb +2 -23
  77. data/lib/couchbase/protostellar/generated/transactions/v1/transactions_services_pb.rb +30 -0
  78. data/lib/couchbase/protostellar/generated/view/v1/view_pb.rb +2 -23
  79. data/lib/couchbase/protostellar/generated/view/v1/view_services_pb.rb +9 -0
  80. data/lib/couchbase/protostellar/request_generator/admin/collection.rb +4 -2
  81. data/lib/couchbase/protostellar/request_generator/admin/query.rb +2 -0
  82. data/lib/couchbase/protostellar/request_generator/kv.rb +1 -1
  83. data/lib/couchbase/protostellar/scope.rb +4 -0
  84. data/lib/couchbase/utils/observability.rb +10 -4
  85. data/lib/couchbase/version.rb +1 -1
  86. metadata +19 -7
  87. data/lib/couchbase/protostellar/generated/routing/v1/routing_pb.rb +0 -52
  88. data/lib/couchbase/protostellar/generated/routing/v1/routing_services_pb.rb +0 -30
@@ -75,6 +75,17 @@ error::error(std::error_code ec,
75
75
  {
76
76
  }
77
77
 
78
+ error::error(std::error_code ec,
79
+ std::string message,
80
+ couchbase::error_context ctx,
81
+ couchbase::node_id node_id)
82
+ : ec_{ ec }
83
+ , message_{ std::move(message) }
84
+ , ctx_{ std::move(ctx) }
85
+ , node_id_{ std::move(node_id) }
86
+ {
87
+ }
88
+
78
89
  auto
79
90
  error::ec() const -> std::error_code
80
91
  {
@@ -103,6 +114,12 @@ error::cause() const -> std::optional<error>
103
114
  return *cause_;
104
115
  }
105
116
 
117
+ auto
118
+ error::node_id() const -> const couchbase::node_id&
119
+ {
120
+ return node_id_;
121
+ }
122
+
106
123
  error::operator bool() const
107
124
  {
108
125
  return ec_.value() != 0;
@@ -164,19 +181,27 @@ make_error(const core::error_context::http& core_ctx) -> couchbase::error
164
181
  auto
165
182
  make_error(const couchbase::core::key_value_error_context& core_ctx) -> couchbase::error
166
183
  {
184
+ // Always preserve the node_id so that callers on both success and error
185
+ // paths can identify which node handled the request.
167
186
  if (!core_ctx.ec()) {
168
- return {};
187
+ return { {}, {}, {}, core_ctx.last_dispatched_to_node_id() };
169
188
  }
170
- return { core_ctx.ec(), {}, internal_error_context::build_error_context(core_ctx) };
189
+ return { core_ctx.ec(),
190
+ {},
191
+ internal_error_context::build_error_context(core_ctx),
192
+ core_ctx.last_dispatched_to_node_id() };
171
193
  }
172
194
 
173
195
  auto
174
196
  make_error(const couchbase::core::subdocument_error_context& core_ctx) -> couchbase::error
175
197
  {
176
198
  if (!core_ctx.ec()) {
177
- return {};
199
+ return { {}, {}, {}, core_ctx.last_dispatched_to_node_id() };
178
200
  }
179
- return { core_ctx.ec(), {}, internal_error_context::build_error_context(core_ctx) };
201
+ return { core_ctx.ec(),
202
+ {},
203
+ internal_error_context::build_error_context(core_ctx),
204
+ core_ctx.last_dispatched_to_node_id() };
180
205
  }
181
206
 
182
207
  auto
@@ -0,0 +1,44 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2026-Present Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #pragma once
19
+
20
+ #include <couchbase/error.hxx>
21
+
22
+ #include <utility>
23
+
24
+ namespace couchbase::core::impl
25
+ {
26
+ /**
27
+ * Forwards (err, result) to the caller's handler, propagating the node_id
28
+ * carried by the error (which make_error populates for KV contexts) onto
29
+ * the result. Using a plain function template avoids the extra std::function
30
+ * allocation a type-erasing wrapper would introduce for the sole purpose of
31
+ * setting node_id - that extra allocation was the source of the 18
32
+ * "Potential memory leak" reports clang-static-analyzer flagged against the
33
+ * previous wrap_with_node_id helper. The handler is taken by forwarding
34
+ * reference and perfect-forwarded to the invocation so that callers moving
35
+ * an rvalue handler consume it without an intermediate copy.
36
+ */
37
+ template<typename Handler, typename Result>
38
+ void
39
+ invoke_with_node_id(Handler&& handler, couchbase::error err, Result result)
40
+ {
41
+ result.node_id(err.node_id());
42
+ std::forward<Handler>(handler)(std::move(err), std::move(result));
43
+ }
44
+ } // namespace couchbase::core::impl
@@ -0,0 +1,110 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2024-Present Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #include "node_id.hxx"
19
+
20
+ #include "core/utils/crc32.hxx"
21
+
22
+ #include <spdlog/fmt/bundled/core.h>
23
+
24
+ #include <cstdint>
25
+ #include <string>
26
+
27
+ namespace couchbase
28
+ {
29
+
30
+ namespace
31
+ {
32
+ /**
33
+ * Produces a stable, opaque hex string from hostname + KV port.
34
+ *
35
+ * Uses CRC32 (already in-tree for vBucket mapping) rather than a
36
+ * cryptographic hash — collision resistance across a handful of cluster
37
+ * nodes is more than sufficient, and this avoids pulling in OpenSSL headers.
38
+ */
39
+ auto
40
+ derive_fallback_id(const std::string& hostname, std::uint16_t port) -> std::string
41
+ {
42
+ auto input = fmt::format("{}:{}", hostname, port);
43
+ auto crc = core::utils::hash_crc32(input.data(), input.size());
44
+ return fmt::format("{:08x}", crc);
45
+ }
46
+ } // namespace
47
+
48
+ node_id::node_id(std::string node_uuid, std::string hostname, std::uint16_t port)
49
+ : node_uuid_{ std::move(node_uuid) }
50
+ , hostname_{ std::move(hostname) }
51
+ , port_{ port }
52
+ , id_{ node_uuid_.empty() ? derive_fallback_id(hostname_, port_) : node_uuid_ }
53
+ {
54
+ }
55
+
56
+ auto
57
+ node_id::id() const -> const std::string&
58
+ {
59
+ return id_;
60
+ }
61
+
62
+ auto
63
+ node_id::node_uuid() const -> const std::string&
64
+ {
65
+ return node_uuid_;
66
+ }
67
+
68
+ auto
69
+ node_id::hostname() const -> const std::string&
70
+ {
71
+ return hostname_;
72
+ }
73
+
74
+ auto
75
+ node_id::port() const -> std::uint16_t
76
+ {
77
+ return port_;
78
+ }
79
+
80
+ node_id::
81
+ operator bool() const
82
+ {
83
+ return !id_.empty();
84
+ }
85
+
86
+ auto
87
+ node_id::operator==(const node_id& other) const -> bool
88
+ {
89
+ return id_ == other.id_;
90
+ }
91
+
92
+ auto
93
+ node_id::operator!=(const node_id& other) const -> bool
94
+ {
95
+ return !(*this == other);
96
+ }
97
+
98
+ auto
99
+ node_id::operator<(const node_id& other) const -> bool
100
+ {
101
+ return id_ < other.id_;
102
+ }
103
+
104
+ auto
105
+ internal_node_id::build(std::string node_uuid, std::string hostname, std::uint16_t port) -> node_id
106
+ {
107
+ return { std::move(node_uuid), std::move(hostname), port };
108
+ }
109
+
110
+ } // namespace couchbase
@@ -0,0 +1,40 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2024-Present Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #pragma once
19
+
20
+ #include <couchbase/node_id.hxx>
21
+
22
+ #include <cstdint>
23
+ #include <string>
24
+
25
+ namespace couchbase
26
+ {
27
+
28
+ /**
29
+ * Internal factory for constructing node_id instances from core-level data.
30
+ *
31
+ * This class is a friend of node_id and provides the only way to construct
32
+ * a non-default node_id outside the couchbase namespace.
33
+ */
34
+ class internal_node_id
35
+ {
36
+ public:
37
+ static auto build(std::string node_uuid, std::string hostname, std::uint16_t port) -> node_id;
38
+ };
39
+
40
+ } // namespace couchbase
@@ -251,45 +251,45 @@ public:
251
251
  app_telemetry_meter_,
252
252
  options_.default_timeout_for(request.type));
253
253
  #endif
254
- cmd->start([start = std::chrono::steady_clock::now(),
255
- self = shared_from_this(),
256
- type,
257
- cmd,
258
- handler =
259
- collector->build_reporter()](operations::http_noop_response&& resp) {
260
- diag::ping_state state = diag::ping_state::ok;
261
- std::optional<std::string> error{};
262
- if (auto ec = resp.ctx.ec; ec) {
263
- if (ec == errc::common::unambiguous_timeout ||
264
- ec == errc::common::ambiguous_timeout) {
265
- state = diag::ping_state::timeout;
266
- } else {
267
- state = diag::ping_state::error;
254
+ cmd->start(
255
+ [start = std::chrono::steady_clock::now(),
256
+ self = shared_from_this(),
257
+ type,
258
+ cmd,
259
+ handler = collector->build_reporter()](operations::http_noop_response&& resp) {
260
+ diag::ping_state state = diag::ping_state::ok;
261
+ std::optional<std::string> error{};
262
+ if (auto ec = resp.ctx.ec; ec) {
263
+ if (ec == errc::common::unambiguous_timeout ||
264
+ ec == errc::common::ambiguous_timeout) {
265
+ state = diag::ping_state::timeout;
266
+ } else {
267
+ state = diag::ping_state::error;
268
+ }
269
+ error.emplace(fmt::format("code={}, message={}, http_code={}",
270
+ ec.value(),
271
+ ec.message(),
272
+ resp.ctx.http_status));
268
273
  }
269
- error.emplace(fmt::format("code={}, message={}, http_code={}",
270
- ec.value(),
271
- ec.message(),
272
- resp.ctx.http_status));
273
- }
274
- auto remote_address = cmd->session_->remote_address();
275
- // If not connected, the remote address will be empty. Better to
276
- // give the user some context on the "attempted" remote address.
277
- if (remote_address.empty()) {
278
- remote_address =
279
- fmt::format("{}:{}", cmd->session_->hostname(), cmd->session_->port());
280
- }
281
- handler->report(
282
- diag::endpoint_ping_info{ type,
283
- cmd->session_->id(),
284
- std::chrono::duration_cast<std::chrono::microseconds>(
285
- std::chrono::steady_clock::now() - start),
286
- remote_address,
287
- cmd->session_->local_address(),
288
- state,
289
- {},
290
- error });
291
- self->check_in(type, cmd->session_);
292
- });
274
+ auto remote_address = cmd->session_->remote_address();
275
+ // If not connected, the remote address will be empty. Better to
276
+ // give the user some context on the "attempted" remote address.
277
+ if (remote_address.empty()) {
278
+ remote_address =
279
+ fmt::format("{}:{}", cmd->session_->hostname(), cmd->session_->port());
280
+ }
281
+ handler->report(
282
+ diag::endpoint_ping_info{ type,
283
+ cmd->session_->id(),
284
+ std::chrono::duration_cast<std::chrono::microseconds>(
285
+ std::chrono::steady_clock::now() - start),
286
+ remote_address,
287
+ cmd->session_->local_address(),
288
+ state,
289
+ {},
290
+ error });
291
+ self->check_in(type, cmd->session_);
292
+ });
293
293
 
294
294
  cmd->set_command_session(session);
295
295
  if (!session->is_connected()) {
@@ -402,9 +402,20 @@ public:
402
402
  return;
403
403
  }
404
404
  if (!session->is_connected()) {
405
- CB_LOG_DEBUG("{} HTTP session never connected. Skipping check-in", session->log_prefix());
406
- return session.reset();
405
+ {
406
+ std::scoped_lock lock(sessions_mutex_);
407
+ if (auto pend_it = pending_sessions_.find(type); pend_it != pending_sessions_.end()) {
408
+ pend_it->second.remove_if([id = session->id()](const auto& s) {
409
+ return !s || s->id() == id;
410
+ });
411
+ }
412
+ }
413
+ CB_LOG_DEBUG("{} HTTP session never connected. Ensured session is not in pending.",
414
+ session->log_prefix());
415
+ return;
407
416
  }
417
+ bool should_stop = false;
418
+ std::chrono::milliseconds idle_timeout{};
408
419
  {
409
420
  std::scoped_lock lock(config_mutex_);
410
421
  if (!session->keep_alive() || !config_.has_node(options_.network,
@@ -412,22 +423,34 @@ public:
412
423
  options_.enable_tls,
413
424
  session->hostname(),
414
425
  session->port())) {
415
- return asio::post(session->get_executor(), [session]() {
416
- session->stop();
417
- });
426
+ should_stop = true;
427
+ } else {
428
+ idle_timeout = options_.idle_http_connection_timeout;
418
429
  }
419
430
  }
431
+ if (should_stop) {
432
+ return asio::post(session->get_executor(), [session]() {
433
+ session->stop();
434
+ });
435
+ }
420
436
  if (!session->is_stopped()) {
421
- session->set_idle(options_.idle_http_connection_timeout);
437
+ // set_idle() arms the idle timer via async_wait — it never calls stop() inline,
438
+ // so on_stop cannot re-enter sessions_mutex_ here. It must precede publication to
439
+ // idle_sessions_ so a concurrent check_out's reset_idle() finds a pending timer.
440
+ session->set_idle(idle_timeout);
422
441
  CB_LOG_DEBUG("{} put HTTP session back to idle connections", session->log_prefix());
423
442
  std::scoped_lock lock(sessions_mutex_);
424
443
  idle_sessions_[type].push_back(session);
425
- busy_sessions_[type].remove_if([id = session->id()](const auto& s) -> bool {
426
- return !s || s->id() == id;
427
- });
428
- pending_sessions_[type].remove_if([id = session->id()](const auto& s) -> bool {
429
- return !s || s->id() == id;
430
- });
444
+ if (auto busy_it = busy_sessions_.find(type); busy_it != busy_sessions_.end()) {
445
+ busy_it->second.remove_if([id = session->id()](const auto& s) -> bool {
446
+ return !s || s->id() == id;
447
+ });
448
+ }
449
+ if (auto pend_it = pending_sessions_.find(type); pend_it != pending_sessions_.end()) {
450
+ pend_it->second.remove_if([id = session->id()](const auto& s) -> bool {
451
+ return !s || s->id() == id;
452
+ });
453
+ }
431
454
  }
432
455
  }
433
456
 
@@ -741,13 +764,30 @@ private:
741
764
  }
742
765
 
743
766
  session->on_stop([type, id = session->id(), self = this->shared_from_this()]() {
744
- const std::scoped_lock inner_lock(self->sessions_mutex_);
745
- self->busy_sessions_[type].remove_if([&id](const auto& s) {
746
- return !s || s->id() == id;
747
- });
748
- self->idle_sessions_[type].remove_if([&id](const auto& s) {
749
- return !s || s->id() == id;
750
- });
767
+ // Declared outside the lock so destructors run after sessions_mutex_ is released.
768
+ // cppcheck-suppress variableScope
769
+ std::vector<std::shared_ptr<http_session>> dropped;
770
+ {
771
+ const std::scoped_lock inner_lock(self->sessions_mutex_);
772
+ for (auto* map :
773
+ { &self->busy_sessions_, &self->idle_sessions_, &self->pending_sessions_ }) {
774
+ auto map_it = map->find(type);
775
+ if (map_it == map->end()) {
776
+ continue;
777
+ }
778
+ auto& list = map_it->second;
779
+ for (auto it = list.begin(); it != list.end();) {
780
+ if (!*it || (*it)->id() == id) {
781
+ if (*it) {
782
+ dropped.push_back(std::move(*it));
783
+ }
784
+ it = list.erase(it);
785
+ } else {
786
+ ++it;
787
+ }
788
+ }
789
+ }
790
+ }
751
791
  });
752
792
  return session;
753
793
  }
@@ -18,6 +18,7 @@
18
18
  #pragma once
19
19
 
20
20
  #include <couchbase/error_codes.hxx>
21
+ #include <couchbase/node_id.hxx>
21
22
 
22
23
  #include "core/error_context/key_value.hxx"
23
24
  #include "core/impl/get_replica.hxx"
@@ -40,6 +41,7 @@ struct get_all_replicas_response {
40
41
  couchbase::cas cas{};
41
42
  std::uint32_t flags{};
42
43
  bool replica{ true };
44
+ couchbase::node_id dispatched_to_node_id{};
43
45
  };
44
46
  key_value_error_context ctx{};
45
47
  std::vector<entry> entries{};
@@ -156,8 +158,12 @@ struct get_all_replicas_request {
156
158
  return;
157
159
  }
158
160
  } else {
159
- ctx->result_.emplace_back(get_all_replicas_response::entry{
160
- std::move(resp.value), resp.cas, resp.flags, true /* replica */ });
161
+ ctx->result_.emplace_back(
162
+ get_all_replicas_response::entry{ std::move(resp.value),
163
+ resp.cas,
164
+ resp.flags,
165
+ true /* replica */,
166
+ resp.ctx.last_dispatched_to_node_id() });
161
167
  }
162
168
  if (ctx->expected_responses_ == 0) {
163
169
  ctx->done_ = true;
@@ -203,8 +209,12 @@ struct get_all_replicas_request {
203
209
  return;
204
210
  }
205
211
  } else {
206
- ctx->result_.emplace_back(get_all_replicas_response::entry{
207
- std::move(resp.value), resp.cas, resp.flags, false /* active */ });
212
+ ctx->result_.emplace_back(
213
+ get_all_replicas_response::entry{ std::move(resp.value),
214
+ resp.cas,
215
+ resp.flags,
216
+ false /* active */,
217
+ resp.ctx.last_dispatched_to_node_id() });
208
218
  }
209
219
  if (ctx->expected_responses_ == 0) {
210
220
  ctx->done_ = true;
@@ -210,14 +210,13 @@ get_projected_request::make_response(key_value_error_context&& ctx,
210
210
  response.ctx.override_ec(errc::common::parsing_failure);
211
211
  return response;
212
212
  }
213
- tao::json::value new_doc;
213
+ tao::json::value new_doc = tao::json::empty_object;
214
214
  for (const auto& projection : projections) {
215
215
  if (auto value_to_apply = subdoc_lookup(full_doc, projection)) {
216
216
  subdoc_apply_projection(new_doc, projection, *value_to_apply, preserve_array_indexes);
217
- } else {
218
- response.ctx.override_ec(errc::key_value::path_not_found);
219
- return response;
220
217
  }
218
+ // We ignore paths that were not found, similar to how we ignore them in the subdoc
219
+ // multi-lookup below.
221
220
  }
222
221
  response.value = utils::json::generate_binary(new_doc);
223
222
  }
@@ -28,6 +28,7 @@
28
28
 
29
29
  #include <couchbase/codec/encoded_value.hxx>
30
30
  #include <couchbase/error_codes.hxx>
31
+ #include <couchbase/node_id.hxx>
31
32
 
32
33
  #include <functional>
33
34
  #include <memory>
@@ -50,6 +51,7 @@ struct lookup_in_all_replicas_response {
50
51
  couchbase::cas cas{};
51
52
  bool deleted{ false };
52
53
  bool is_replica{ true };
54
+ couchbase::node_id dispatched_to_node_id{};
53
55
  };
54
56
  subdocument_error_context ctx{};
55
57
  std::vector<entry> entries{};
@@ -203,6 +205,7 @@ struct lookup_in_all_replicas_request {
203
205
  top_entry.cas = resp.cas;
204
206
  top_entry.deleted = resp.deleted;
205
207
  top_entry.is_replica = true;
208
+ top_entry.dispatched_to_node_id = resp.ctx.last_dispatched_to_node_id();
206
209
  for (auto& field : resp.fields) {
207
210
  lookup_in_all_replicas_response::entry::lookup_in_entry lookup_in_entry{};
208
211
  lookup_in_entry.path = field.path;
@@ -267,6 +270,7 @@ struct lookup_in_all_replicas_request {
267
270
  top_entry.cas = resp.cas;
268
271
  top_entry.deleted = resp.deleted;
269
272
  top_entry.is_replica = false;
273
+ top_entry.dispatched_to_node_id = resp.ctx.last_dispatched_to_node_id();
270
274
  for (auto& field : resp.fields) {
271
275
  lookup_in_all_replicas_response::entry::lookup_in_entry lookup_in_entry{};
272
276
  lookup_in_entry.path = field.path;
@@ -20,6 +20,7 @@
20
20
  #include "core/cluster_options.hxx"
21
21
  #include "core/logger/logger.hxx"
22
22
  #include "core/operations/management/error_utils.hxx"
23
+ #include "core/utils/contains_string.hxx"
23
24
  #include "core/utils/duration_parser.hxx"
24
25
  #include "core/utils/json.hxx"
25
26
 
@@ -28,13 +29,11 @@
28
29
  #include <gsl/assert>
29
30
  #include <tao/json/value.hpp>
30
31
 
31
- #include <regex>
32
-
33
32
  namespace couchbase::core::operations
34
33
  {
35
34
  auto
36
- query_request::encode_to(query_request::encoded_request_type& encoded,
37
- http_context& context) -> std::error_code
35
+ query_request::encode_to(query_request::encoded_request_type& encoded, http_context& context)
36
+ -> std::error_code
38
37
  {
39
38
  ctx_.emplace(context);
40
39
  tao::json::value body{
@@ -209,8 +208,8 @@ query_request::encode_to(query_request::encoded_request_type& encoded,
209
208
  }
210
209
 
211
210
  auto
212
- query_request::make_response(error_context::query&& ctx,
213
- const encoded_response_type& encoded) -> query_response
211
+ query_request::make_response(error_context::query&& ctx, const encoded_response_type& encoded)
212
+ -> query_response
214
213
  {
215
214
  query_response response{ std::move(ctx) };
216
215
  response.ctx.statement = statement;
@@ -374,13 +373,14 @@ query_request::make_response(error_context::query&& ctx,
374
373
  response.ctx.ec = errc::common::index_exists;
375
374
  break;
376
375
  case 5000: /* IKey: "Internal Error" */
377
- if (std::regex_search(response.ctx.first_error_message,
378
- std::regex{ ".*[iI]ndex .*already exist.*" })) {
376
+ if (utils::contains_string(response.ctx.first_error_message, "index", true) &&
377
+ utils::contains_string(response.ctx.first_error_message, "already exist", true)) {
379
378
  response.ctx.ec = errc::common::index_exists;
380
- } else if (response.ctx.first_error_message.find("Index does not exist") !=
381
- std::string::npos ||
382
- std::regex_search(response.ctx.first_error_message,
383
- std::regex{ ".*[iI]ndex .*[nN]ot [fF]ound.*" })) {
379
+ } else if (utils::contains_string(response.ctx.first_error_message,
380
+ "Index does not exist") ||
381
+ (utils::contains_string(response.ctx.first_error_message, "index", true) &&
382
+ utils::contains_string(
383
+ response.ctx.first_error_message, "not found", true))) {
384
384
  response.ctx.ec = errc::common::index_not_found;
385
385
  } else if (response.ctx.first_error_message.find("Bucket Not Found") !=
386
386
  std::string::npos) {
@@ -24,13 +24,11 @@
24
24
  #include <spdlog/fmt/bundled/core.h>
25
25
  #include <tao/json/value.hpp>
26
26
 
27
- #include <regex>
28
-
29
27
  namespace couchbase::core::operations::management
30
28
  {
31
29
  auto
32
- collection_create_request::encode_to(encoded_request_type& encoded,
33
- http_context& /*context*/) const -> std::error_code
30
+ collection_create_request::encode_to(encoded_request_type& encoded, http_context& /*context*/) const
31
+ -> std::error_code
34
32
  {
35
33
  encoded.method = "POST";
36
34
  encoded.path = fmt::format("/pools/default/buckets/{}/scopes/{}/collections",
@@ -58,18 +56,20 @@ collection_create_request::make_response(error_context::http&& ctx,
58
56
  {
59
57
  collection_create_response response{ std::move(ctx) };
60
58
  if (!response.ctx.ec) {
61
- switch (encoded.status_code) {
59
+ switch (const auto& body = encoded.body.data(); encoded.status_code) {
62
60
  case 400: {
63
- const std::regex collection_exists("Collection with name .+ already exists");
64
- if (std::regex_search(encoded.body.data(), collection_exists)) {
61
+ const auto prefix_pos = body.find("Collection with name ");
62
+ if (prefix_pos != std::string_view::npos &&
63
+ body.find(" already exists", prefix_pos) != std::string_view::npos) {
65
64
  response.ctx.ec = errc::management::collection_exists;
66
65
  } else {
67
66
  response.ctx.ec = errc::common::invalid_argument;
68
67
  }
69
68
  } break;
70
69
  case 404: {
71
- const std::regex scope_not_found("Scope with name .+ is not found");
72
- if (std::regex_search(encoded.body.data(), scope_not_found)) {
70
+ const auto prefix_pos = body.find("Scope with name ");
71
+ if (prefix_pos != std::string_view::npos &&
72
+ body.find(" is not found", prefix_pos) != std::string_view::npos) {
73
73
  response.ctx.ec = errc::common::scope_not_found;
74
74
  } else {
75
75
  response.ctx.ec = errc::common::bucket_not_found;
@@ -78,7 +78,7 @@ collection_create_request::make_response(error_context::http&& ctx,
78
78
  case 200: {
79
79
  tao::json::value payload{};
80
80
  try {
81
- payload = utils::json::parse(encoded.body.data());
81
+ payload = utils::json::parse(body);
82
82
  } catch (const tao::pegtl::parse_error&) {
83
83
  response.ctx.ec = errc::common::parsing_failure;
84
84
  return response;
@@ -86,7 +86,7 @@ collection_create_request::make_response(error_context::http&& ctx,
86
86
  response.uid = std::stoull(payload.at("uid").get_string(), nullptr, 16);
87
87
  } break;
88
88
  default:
89
- response.ctx.ec = extract_common_error_code(encoded.status_code, encoded.body.data());
89
+ response.ctx.ec = extract_common_error_code(encoded.status_code, body);
90
90
  break;
91
91
  }
92
92
  }