couchbase 3.4.1 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/couchbase/CMakeLists.txt +2 -0
  4. data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +4 -0
  5. data/ext/couchbase/core/cluster_options.hxx +0 -1
  6. data/ext/couchbase/core/config_profile.cxx +23 -1
  7. data/ext/couchbase/core/config_profile.hxx +2 -12
  8. data/ext/couchbase/core/impl/analytics.cxx +236 -0
  9. data/ext/couchbase/core/impl/cluster.cxx +0 -1
  10. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +5 -3
  11. data/ext/couchbase/core/impl/query.cxx +5 -5
  12. data/ext/couchbase/core/io/dns_client.cxx +225 -0
  13. data/ext/couchbase/core/io/dns_client.hxx +19 -188
  14. data/ext/couchbase/core/transactions/active_transaction_record.hxx +2 -2
  15. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +3 -0
  16. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +1 -1
  17. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +12 -12
  18. data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +7 -1
  19. data/ext/couchbase/core/transactions/transaction_context.cxx +1 -0
  20. data/ext/couchbase/core/transactions/transactions_cleanup.cxx +144 -155
  21. data/ext/couchbase/core/utils/connection_string.cxx +10 -3
  22. data/ext/couchbase/core/utils/connection_string.hxx +3 -3
  23. data/ext/couchbase/couchbase/analytics_error_context.hxx +143 -0
  24. data/ext/couchbase/couchbase/analytics_meta_data.hxx +155 -0
  25. data/ext/couchbase/couchbase/analytics_metrics.hxx +163 -0
  26. data/ext/couchbase/couchbase/analytics_options.hxx +359 -0
  27. data/ext/couchbase/couchbase/analytics_result.hxx +102 -0
  28. data/ext/couchbase/couchbase/analytics_scan_consistency.hxx +46 -0
  29. data/ext/couchbase/couchbase/analytics_status.hxx +41 -0
  30. data/ext/couchbase/couchbase/analytics_warning.hxx +85 -0
  31. data/ext/couchbase/couchbase/cluster.hxx +33 -0
  32. data/ext/couchbase/couchbase/fmt/analytics_status.hxx +76 -0
  33. data/ext/couchbase/couchbase/query_options.hxx +0 -1
  34. data/ext/couchbase/couchbase/scope.hxx +33 -0
  35. data/ext/couchbase/couchbase/transactions/attempt_context.hxx +1 -1
  36. data/ext/couchbase/test/CMakeLists.txt +1 -2
  37. data/ext/couchbase/test/test_helper.hxx +1 -1
  38. data/ext/couchbase/test/test_integration_analytics.cxx +289 -13
  39. data/ext/couchbase/test/test_integration_crud.cxx +8 -1
  40. data/ext/couchbase/test/test_integration_examples.cxx +41 -0
  41. data/ext/couchbase/test/test_integration_management.cxx +15 -3
  42. data/ext/couchbase/test/test_integration_search.cxx +601 -0
  43. data/ext/couchbase/test/test_transaction_transaction_simple.cxx +73 -0
  44. data/ext/couchbase/test/test_unit_config_profiles.cxx +12 -12
  45. data/ext/couchbase/test/test_unit_connection_string.cxx +35 -0
  46. data/ext/couchbase/third_party/snappy/CMakeLists.txt +150 -27
  47. data/ext/couchbase/third_party/snappy/cmake/config.h.in +28 -24
  48. data/ext/couchbase/third_party/snappy/snappy-internal.h +189 -25
  49. data/ext/couchbase/third_party/snappy/snappy-sinksource.cc +26 -9
  50. data/ext/couchbase/third_party/snappy/snappy-sinksource.h +11 -11
  51. data/ext/couchbase/third_party/snappy/snappy-stubs-internal.cc +1 -1
  52. data/ext/couchbase/third_party/snappy/snappy-stubs-internal.h +227 -308
  53. data/ext/couchbase/third_party/snappy/snappy-stubs-public.h.in +0 -11
  54. data/ext/couchbase/third_party/snappy/snappy.cc +1176 -410
  55. data/ext/couchbase/third_party/snappy/snappy.h +19 -4
  56. data/ext/couchbase.cxx +27 -6
  57. data/ext/revisions.rb +3 -3
  58. data/lib/couchbase/cluster.rb +13 -9
  59. data/lib/couchbase/cluster_registry.rb +7 -2
  60. data/lib/couchbase/configuration.rb +3 -4
  61. data/lib/couchbase/options.rb +85 -2
  62. data/lib/couchbase/search_options.rb +158 -240
  63. data/lib/couchbase/version.rb +1 -1
  64. metadata +17 -6
  65. data/ext/couchbase/core/CMakeLists.txt +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 738d96522993c221d75dc83546dc6c9f5447309fbbe4969f6fb92719b0427e78
4
- data.tar.gz: 5189c37096e7193f5756050a9713bd7d8f75acd8949e5afe2402fc1834ed538f
3
+ metadata.gz: 78899f9fd7614787a46a54ca185e869cc93a7714ff0a1ef4c38feef98d7d54be
4
+ data.tar.gz: f42e90dc0e70b2f690f9c4f7dffe3cd9675db7d8f9025bb5db315b6e59407199
5
5
  SHA512:
6
- metadata.gz: 85632d1eea3c7139c246f80fadbe8135aacd4b550fdd49a0e33a98342846d39d7d1a7b45abd2d03f1ecbecb9fbdb3401d5ef16b2c52e4bfa3c08edcebfa976e3
7
- data.tar.gz: 34202c48810d9ebc8c9930a1f860acafa9a97b4de8304a22e56fa811a9462f02a857d277fb3abf1572226244ed05aeaafd61d23689a74aad6cdae83c33669fca
6
+ metadata.gz: 804b0a4c05754a5349b19f4b06ee1017f62790e2f5f50696639d9cfc1b785d4f43d688cb05c325f5e10cb1e36fca1e4775c93f91cbf10ad600a5f4755a091a28
7
+ data.tar.gz: cbb64fe3e1c2f5925584032cd69bb626e87c9ff61c7735a570a7e291287874076e366e2282e15d432c18832b9853c749d806cab28aa65716dc91ab835910bd6b
data/README.md CHANGED
@@ -23,7 +23,7 @@ The library has been tested with MRI 3.0, 3.1 and 3.2. Supported platforms are L
23
23
  Add this line to your application's Gemfile:
24
24
 
25
25
  ```ruby
26
- gem "couchbase", "3.4.1"
26
+ gem "couchbase", "3.4.2"
27
27
  ```
28
28
 
29
29
  And then execute:
@@ -139,7 +139,7 @@ Now the API reference is accessible using a web browser (where `VERSION` is the
139
139
 
140
140
  The gem is available as open source under the terms of the [Apache2 License](https://opensource.org/licenses/Apache-2.0).
141
141
 
142
- Copyright 2011-2021 Couchbase, Inc.
142
+ Copyright 2011-Present Couchbase, Inc.
143
143
 
144
144
  Licensed under the Apache License, Version 2.0 (the "License");
145
145
  you may not use this file except in compliance with the License.
@@ -255,6 +255,7 @@ set(couchbase_cxx_client_FILES
255
255
  core/utils/mutation_token.cxx
256
256
  core/utils/split_string.cxx
257
257
  core/utils/url_codec.cxx
258
+ core/impl/analytics.cxx
258
259
  core/impl/analytics_error_category.cxx
259
260
  core/impl/append.cxx
260
261
  core/impl/best_effort_retry_strategy.cxx
@@ -321,6 +322,7 @@ set(couchbase_cxx_client_FILES
321
322
  core/impl/upsert.cxx
322
323
  core/impl/view_error_category.cxx
323
324
  core/impl/watch_query_indexes.cxx
325
+ core/io/dns_client.cxx
324
326
  core/io/dns_config.cxx
325
327
  core/io/http_parser.cxx
326
328
  core/io/mcbp_message.cxx
@@ -18,6 +18,10 @@ set(PEGTL_BUILD_TESTS OFF CACHE BOOL "" FORCE)
18
18
  set(PEGTL_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
19
19
  add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/snappy)
20
20
  set_target_properties(snappy PROPERTIES POSITION_INDEPENDENT_CODE ON)
21
+ if(NOT MSVC)
22
+ # https://github.com/google/snappy/pull/156
23
+ target_compile_options(snappy PRIVATE -Wno-sign-compare)
24
+ endif()
21
25
 
22
26
  set(HDR_LOG_REQUIRED OFF CACHE BOOL "" FORCE)
23
27
  set(HDR_HISTOGRAM_BUILD_SHARED OFF CACHE BOOL "" FORCE)
@@ -53,7 +53,6 @@ struct cluster_options {
53
53
  std::chrono::milliseconds analytics_timeout = timeout_defaults::analytics_timeout;
54
54
  std::chrono::milliseconds search_timeout = timeout_defaults::search_timeout;
55
55
  std::chrono::milliseconds management_timeout = timeout_defaults::management_timeout;
56
- std::chrono::milliseconds dns_srv_timeout = timeout_defaults::dns_srv_timeout;
57
56
 
58
57
  bool enable_tls{ false };
59
58
  std::string trust_certificate{};
@@ -22,4 +22,26 @@ couchbase::core::known_profiles()
22
22
  {
23
23
  static couchbase::core::config_profiles profiles{};
24
24
  return profiles;
25
- }
25
+ }
26
+
27
+ void
28
+ couchbase::core::development_profile::apply(couchbase::core::cluster_options& opts)
29
+ {
30
+ opts.key_value_timeout = std::chrono::seconds(20);
31
+ opts.key_value_durable_timeout = std::chrono::seconds(20);
32
+ opts.connect_timeout = std::chrono::seconds(20);
33
+ opts.view_timeout = std::chrono::minutes(2);
34
+ opts.query_timeout = std::chrono::minutes(2);
35
+ opts.analytics_timeout = std::chrono::minutes(2);
36
+ opts.search_timeout = std::chrono::minutes(2);
37
+ opts.management_timeout = std::chrono::minutes(2);
38
+
39
+ // C++SDK specific
40
+ opts.dns_config = couchbase::core::io::dns::dns_config{
41
+ opts.dns_config.nameserver(),
42
+ opts.dns_config.port(),
43
+ std::chrono::seconds(20), // timeout to make DNS-SRV query
44
+ };
45
+ opts.resolve_timeout = std::chrono::seconds(20); // timeout to resolve hostnames
46
+ opts.bootstrap_timeout = std::chrono::minutes(2); // overall timeout to bootstrap
47
+ }
@@ -36,17 +36,7 @@ class config_profile
36
36
  class development_profile : public config_profile
37
37
  {
38
38
  public:
39
- void apply(couchbase::core::cluster_options& opts) override
40
- {
41
- opts.key_value_timeout = std::chrono::milliseconds(20000); // 20 sec kv timeout.
42
- opts.key_value_durable_timeout = std::chrono::milliseconds(20000); // 20 sec durable kv timeout.
43
- opts.connect_timeout = std::chrono::milliseconds(20000); // 20 sec connect timeout.
44
- opts.view_timeout = std::chrono::milliseconds(120000); // 2 minute view timeout
45
- opts.query_timeout = std::chrono::milliseconds(120000); // 2 minute query timeout
46
- opts.analytics_timeout = std::chrono::milliseconds(120000); // 2 minute analytics timeout
47
- opts.search_timeout = std::chrono::milliseconds(120000); // 2 minute search timeout
48
- opts.management_timeout = std::chrono::milliseconds(120000); // 2 minute management timeout
49
- }
39
+ void apply(couchbase::core::cluster_options& opts) override;
50
40
  };
51
41
 
52
42
  // this class just registers the known profiles defined above, and allows access to them.
@@ -89,4 +79,4 @@ class config_profiles
89
79
  config_profiles&
90
80
  known_profiles();
91
81
 
92
- } // namespace couchbase::core
82
+ } // namespace couchbase::core
@@ -0,0 +1,236 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2020-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 <couchbase/cluster.hxx>
19
+ #include <couchbase/error_codes.hxx>
20
+
21
+ #include "core/cluster.hxx"
22
+ #include "core/operations/document_analytics.hxx"
23
+
24
+ namespace couchbase
25
+ {
26
+ static analytics_error_context
27
+ build_context(core::operations::analytics_response& resp)
28
+ {
29
+ return {
30
+ resp.ctx.ec,
31
+ resp.ctx.last_dispatched_to,
32
+ resp.ctx.last_dispatched_from,
33
+ resp.ctx.retry_attempts,
34
+ std::move(resp.ctx.retry_reasons),
35
+ resp.ctx.first_error_code,
36
+ std::move(resp.ctx.first_error_message),
37
+ std::move(resp.ctx.client_context_id),
38
+ std::move(resp.ctx.statement),
39
+ std::move(resp.ctx.parameters),
40
+ std::move(resp.ctx.method),
41
+ std::move(resp.ctx.path),
42
+ resp.ctx.http_status,
43
+ std::move(resp.ctx.http_body),
44
+ std::move(resp.ctx.hostname),
45
+ resp.ctx.port,
46
+ };
47
+ }
48
+
49
+ static analytics_status
50
+ map_status(core::operations::analytics_response::analytics_status status)
51
+ {
52
+ switch (status) {
53
+ case core::operations::analytics_response::running:
54
+ return analytics_status::running;
55
+ case core::operations::analytics_response::success:
56
+ return analytics_status::success;
57
+ case core::operations::analytics_response::errors:
58
+ return analytics_status::errors;
59
+ case core::operations::analytics_response::completed:
60
+ return analytics_status::completed;
61
+ case core::operations::analytics_response::stopped:
62
+ return analytics_status::stopped;
63
+ case core::operations::analytics_response::timedout:
64
+ return analytics_status::timeout;
65
+ case core::operations::analytics_response::closed:
66
+ return analytics_status::closed;
67
+ case core::operations::analytics_response::fatal:
68
+ return analytics_status::fatal;
69
+ case core::operations::analytics_response::aborted:
70
+ return analytics_status::aborted;
71
+ case core::operations::analytics_response::unknown:
72
+ return analytics_status::unknown;
73
+ }
74
+ return analytics_status::unknown;
75
+ }
76
+
77
+ static std::optional<couchbase::core::analytics_scan_consistency>
78
+ map_scan_consistency(std::optional<couchbase::analytics_scan_consistency> consistency)
79
+ {
80
+ if (consistency.has_value()) {
81
+ switch (consistency.value()) {
82
+ case analytics_scan_consistency::not_bounded:
83
+ return couchbase::core::analytics_scan_consistency::not_bounded;
84
+ case analytics_scan_consistency::request_plus:
85
+ return couchbase::core::analytics_scan_consistency::request_plus;
86
+ }
87
+ }
88
+ return {};
89
+ }
90
+
91
+ static std::vector<codec::binary>
92
+ map_rows(const core::operations::analytics_response& resp)
93
+ {
94
+ std::vector<codec::binary> rows;
95
+ rows.reserve(resp.rows.size());
96
+ for (const auto& row : resp.rows) {
97
+ rows.emplace_back(core::utils::to_binary(row));
98
+ }
99
+ return rows;
100
+ }
101
+
102
+ static std::vector<analytics_warning>
103
+ map_warnings(core::operations::analytics_response& resp)
104
+ {
105
+ if (resp.meta.warnings.empty()) {
106
+ return {};
107
+ }
108
+ std::vector<analytics_warning> warnings;
109
+ warnings.reserve(resp.meta.warnings.size());
110
+ for (auto& warning : resp.meta.warnings) {
111
+ warnings.emplace_back(warning.code, std::move(warning.message));
112
+ }
113
+ return warnings;
114
+ }
115
+
116
+ static analytics_metrics
117
+ map_metrics(const core::operations::analytics_response& resp)
118
+ {
119
+ return analytics_metrics{
120
+ resp.meta.metrics.elapsed_time, resp.meta.metrics.execution_time, resp.meta.metrics.result_count,
121
+ resp.meta.metrics.result_size, resp.meta.metrics.processed_objects, resp.meta.metrics.error_count,
122
+ resp.meta.metrics.warning_count,
123
+ };
124
+ }
125
+
126
+ static std::optional<std::vector<std::byte>>
127
+ map_signature(core::operations::analytics_response& resp)
128
+ {
129
+ if (!resp.meta.signature) {
130
+ return {};
131
+ }
132
+ return core::utils::to_binary(resp.meta.signature.value());
133
+ }
134
+
135
+ static analytics_result
136
+ build_result(core::operations::analytics_response& resp)
137
+ {
138
+ return {
139
+ analytics_meta_data{
140
+ std::move(resp.meta.request_id),
141
+ std::move(resp.meta.client_context_id),
142
+ map_status(resp.meta.status),
143
+ map_warnings(resp),
144
+ map_metrics(resp),
145
+ map_signature(resp),
146
+ },
147
+ map_rows(resp),
148
+ };
149
+ }
150
+
151
+ static core::operations::analytics_request
152
+ build_analytics_request(std::string statement,
153
+ analytics_options::built options,
154
+ std::optional<std::string> bucket_name,
155
+ std::optional<std::string> scope_name)
156
+ {
157
+ core::operations::analytics_request request{
158
+ std::move(statement),
159
+ options.readonly,
160
+ options.priority,
161
+ std::move(bucket_name),
162
+ std::move(scope_name),
163
+ {},
164
+ map_scan_consistency(options.scan_consistency),
165
+ {},
166
+ {},
167
+ {},
168
+ {},
169
+ std::move(options.client_context_id),
170
+ options.timeout,
171
+ };
172
+ if (!options.raw.empty()) {
173
+ for (auto& [name, value] : options.raw) {
174
+ request.raw[name] = std::move(value);
175
+ }
176
+ }
177
+ if (!options.positional_parameters.empty()) {
178
+ for (auto& value : options.positional_parameters) {
179
+ request.positional_parameters.emplace_back(std::move(value));
180
+ }
181
+ }
182
+ if (!options.named_parameters.empty()) {
183
+ for (auto& [name, value] : options.named_parameters) {
184
+ request.named_parameters[name] = std::move(value);
185
+ }
186
+ }
187
+ return request;
188
+ }
189
+
190
+ void
191
+ cluster::analytics_query(std::string statement, const analytics_options& options, analytics_handler&& handler) const
192
+ {
193
+ auto request = build_analytics_request(std::move(statement), options.build(), {}, {});
194
+
195
+ core_->execute(std::move(request), [handler = std::move(handler)](core::operations::analytics_response resp) mutable {
196
+ auto r = std::move(resp);
197
+ return handler(build_context(r), build_result(r));
198
+ });
199
+ }
200
+
201
+ auto
202
+ cluster::analytics_query(std::string statement, const analytics_options& options) const
203
+ -> std::future<std::pair<analytics_error_context, analytics_result>>
204
+ {
205
+ auto barrier = std::make_shared<std::promise<std::pair<analytics_error_context, analytics_result>>>();
206
+ auto future = barrier->get_future();
207
+ analytics_query(std::move(statement), options, [barrier](auto ctx, auto result) {
208
+ barrier->set_value({ std::move(ctx), std::move(result) });
209
+ });
210
+ return future;
211
+ }
212
+
213
+ void
214
+ scope::analytics_query(std::string statement, const analytics_options& options, analytics_handler&& handler) const
215
+ {
216
+ auto request = build_analytics_request(std::move(statement), options.build(), bucket_name_, name_);
217
+
218
+ core_->execute(std::move(request), [handler = std::move(handler)](core::operations::analytics_response resp) mutable {
219
+ auto r = std::move(resp);
220
+ return handler(build_context(r), build_result(r));
221
+ });
222
+ }
223
+
224
+ auto
225
+ scope::analytics_query(std::string statement, const analytics_options& options) const
226
+ -> std::future<std::pair<analytics_error_context, analytics_result>>
227
+ {
228
+ auto barrier = std::make_shared<std::promise<std::pair<analytics_error_context, analytics_result>>>();
229
+ auto future = barrier->get_future();
230
+ analytics_query(std::move(statement), options, [barrier](auto ctx, auto result) {
231
+ barrier->set_value({ std::move(ctx), std::move(result) });
232
+ });
233
+ return future;
234
+ }
235
+
236
+ } // namespace couchbase
@@ -95,7 +95,6 @@ options_to_origin(const std::string& connection_string, const couchbase::cluster
95
95
  if (opts.dns.nameserver) {
96
96
  user_options.dns_config =
97
97
  io::dns::dns_config(opts.dns.nameserver.value(), opts.dns.port.value_or(io::dns::dns_config::default_port), opts.dns.timeout);
98
- user_options.dns_srv_timeout = opts.dns.timeout;
99
98
  }
100
99
  user_options.enable_clustermap_notification = opts.behavior.enable_clustermap_notification;
101
100
  user_options.show_queries = opts.behavior.show_queries;
@@ -16,11 +16,13 @@
16
16
  */
17
17
 
18
18
  #include "dns_srv_tracker.hxx"
19
- #include "../logger/logger.hxx"
20
- #include "../topology/configuration.hxx"
19
+
20
+ #include "core/logger/logger.hxx"
21
+ #include "core/utils/join_strings.hxx"
21
22
 
22
23
  #include <asio/bind_executor.hpp>
23
24
  #include <asio/io_context.hpp>
25
+ #include <asio/post.hpp>
24
26
 
25
27
  #include <memory>
26
28
 
@@ -44,7 +46,7 @@ dns_srv_tracker::get_srv_nodes(utils::movable_function<void(origin::node_list, s
44
46
  address_,
45
47
  service_,
46
48
  config_,
47
- [self = shared_from_this(), callback = std::move(callback)](couchbase::core::io::dns::dns_client::dns_srv_response&& resp) mutable {
49
+ [self = shared_from_this(), callback = std::move(callback)](couchbase::core::io::dns::dns_srv_response&& resp) mutable {
48
50
  origin::node_list nodes;
49
51
  if (resp.ec) {
50
52
  CB_LOG_WARNING("failed to fetch DNS SRV records for \"{}\" ({}), assuming that cluster is listening this address",
@@ -152,10 +152,10 @@ build_result(operations::query_response& resp)
152
152
  }
153
153
 
154
154
  static core::operations::query_request
155
- build_query_request(query_options::built options)
155
+ build_query_request(std::string statement, query_options::built options)
156
156
  {
157
157
  operations::query_request request{
158
- "",
158
+ std::move(statement),
159
159
  options.adhoc,
160
160
  options.metrics,
161
161
  options.readonly,
@@ -222,8 +222,9 @@ build_transaction_query_result(operations::query_response resp, std::error_code
222
222
  core::operations::query_request
223
223
  build_transaction_query_request(query_options::built opts)
224
224
  {
225
- return build_query_request(opts);
225
+ return build_query_request("", opts);
226
226
  }
227
+
227
228
  void
228
229
  initiate_query_operation(std::shared_ptr<couchbase::core::cluster> core,
229
230
  std::string statement,
@@ -231,8 +232,7 @@ initiate_query_operation(std::shared_ptr<couchbase::core::cluster> core,
231
232
  query_options::built options,
232
233
  query_handler&& handler)
233
234
  {
234
- auto request = build_query_request(options);
235
- request.statement = std::move(statement);
235
+ auto request = build_query_request(std::move(statement), options);
236
236
  if (query_context) {
237
237
  request.query_context = std::move(query_context);
238
238
  }
@@ -0,0 +1,225 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2020-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 "dns_client.hxx"
19
+
20
+ #include "core/logger/logger.hxx"
21
+ #include "core/utils/join_strings.hxx"
22
+ #include "dns_codec.hxx"
23
+ #include "dns_config.hxx"
24
+
25
+ #include <couchbase/error_codes.hxx>
26
+
27
+ #include <asio/ip/tcp.hpp>
28
+ #include <asio/read.hpp>
29
+ #include <asio/write.hpp>
30
+
31
+ #include <memory>
32
+ #include <sstream>
33
+
34
+ namespace couchbase::core::io::dns
35
+ {
36
+ class dns_srv_command : public std::enable_shared_from_this<dns_srv_command>
37
+ {
38
+ public:
39
+ dns_srv_command(asio::io_context& ctx,
40
+ const std::string& name,
41
+ const std::string& service,
42
+ const asio::ip::address& address,
43
+ std::uint16_t port,
44
+ utils::movable_function<void(couchbase::core::io::dns::dns_srv_response&& resp)> handler)
45
+ : deadline_(ctx)
46
+ , udp_deadline_(ctx)
47
+ , udp_(ctx)
48
+ , tcp_(ctx)
49
+ , address_(address)
50
+ , port_(port)
51
+ , handler_(std::move(handler))
52
+ {
53
+ static std::string protocol{ "_tcp" };
54
+ dns_message request{};
55
+ question_record qr;
56
+ qr.klass = resource_class::in;
57
+ qr.type = resource_type::srv;
58
+ qr.name.labels.push_back(service);
59
+ qr.name.labels.push_back(protocol);
60
+ std::string label;
61
+ std::istringstream name_stream(name);
62
+ while (std::getline(name_stream, label, '.')) {
63
+ qr.name.labels.push_back(label);
64
+ }
65
+ request.questions.emplace_back(qr);
66
+ send_buf_ = dns_codec::encode(request);
67
+ }
68
+
69
+ void execute(std::chrono::milliseconds total_timeout, std::chrono::milliseconds udp_timeout)
70
+ {
71
+ asio::ip::udp::endpoint endpoint(address_, port_);
72
+ udp_.open(endpoint.protocol());
73
+ udp_.async_send_to(
74
+ asio::buffer(send_buf_), endpoint, [self = shared_from_this()](std::error_code ec1, std::size_t /* bytes_transferred */) mutable {
75
+ if (ec1) {
76
+ self->udp_deadline_.cancel();
77
+ CB_LOG_DEBUG("DNS UDP write operation has got error {}, retrying with TCP", ec1.message());
78
+ return self->retry_with_tcp();
79
+ }
80
+
81
+ self->recv_buf_.resize(512);
82
+ self->udp_.async_receive_from(
83
+ asio::buffer(self->recv_buf_), self->udp_sender_, [self](std::error_code ec2, std::size_t bytes_transferred) mutable {
84
+ self->udp_deadline_.cancel();
85
+ if (ec2) {
86
+ CB_LOG_DEBUG("DNS UDP read operation has got error {}, retrying with TCP", ec2.message());
87
+ return self->retry_with_tcp();
88
+ }
89
+ self->recv_buf_.resize(bytes_transferred);
90
+ const dns_message message = dns_codec::decode(self->recv_buf_);
91
+ if (message.header.flags.tc == truncation::yes) {
92
+ self->udp_.close();
93
+ CB_LOG_DEBUG("DNS UDP read operation returned truncated response, retrying with TCP");
94
+ return self->retry_with_tcp();
95
+ }
96
+ self->deadline_.cancel();
97
+ dns_srv_response resp{ ec2 };
98
+ resp.targets.reserve(message.answers.size());
99
+ for (const auto& answer : message.answers) {
100
+ resp.targets.emplace_back(dns_srv_response::address{ utils::join_strings(answer.target.labels, "."), answer.port });
101
+ }
102
+ CB_LOG_DEBUG("DNS UDP returned {} records", resp.targets.size());
103
+ return self->handler_(std::move(resp));
104
+ });
105
+ });
106
+ udp_deadline_.expires_after(udp_timeout);
107
+ deadline_.async_wait([self = shared_from_this()](std::error_code ec) {
108
+ if (ec == asio::error::operation_aborted) {
109
+ return;
110
+ }
111
+ CB_LOG_DEBUG("DNS UDP deadline has been reached, cancelling UDP operation and fall back to TCP");
112
+ self->udp_.cancel();
113
+ return self->retry_with_tcp();
114
+ });
115
+
116
+ deadline_.expires_after(total_timeout);
117
+ deadline_.async_wait([self = shared_from_this()](std::error_code ec) {
118
+ if (ec == asio::error::operation_aborted) {
119
+ return;
120
+ }
121
+ CB_LOG_DEBUG("DNS deadline has been reached, cancelling in-flight operations (tcp.is_open={})", self->tcp_.is_open());
122
+ self->udp_.cancel();
123
+ if (self->tcp_.is_open()) {
124
+ self->tcp_.cancel();
125
+ }
126
+ });
127
+ }
128
+
129
+ private:
130
+ void retry_with_tcp()
131
+ {
132
+ if (bool expected_state{ false }; !retrying_with_tcp_.compare_exchange_strong(expected_state, true)) {
133
+ return;
134
+ }
135
+
136
+ const asio::ip::tcp::no_delay no_delay(true);
137
+ std::error_code ignore_ec;
138
+ tcp_.set_option(no_delay, ignore_ec);
139
+ const asio::ip::tcp::endpoint endpoint(address_, port_);
140
+ tcp_.async_connect(endpoint, [self = shared_from_this()](std::error_code ec1) mutable {
141
+ if (ec1) {
142
+ self->deadline_.cancel();
143
+ CB_LOG_DEBUG("DNS TCP connection has been aborted, {}", ec1.message());
144
+ return self->handler_({ ec1 });
145
+ }
146
+ auto send_size = static_cast<std::uint16_t>(self->send_buf_.size());
147
+ self->send_buf_.insert(self->send_buf_.begin(), static_cast<std::uint8_t>(send_size & 0xffU));
148
+ self->send_buf_.insert(self->send_buf_.begin(), static_cast<std::uint8_t>(send_size >> 8U));
149
+ asio::async_write(
150
+ self->tcp_, asio::buffer(self->send_buf_), [self](std::error_code ec2, std::size_t /* bytes_transferred */) mutable {
151
+ if (ec2) {
152
+ CB_LOG_DEBUG("DNS TCP write operation has been aborted, {}", ec2.message());
153
+ self->deadline_.cancel();
154
+ if (ec2 == asio::error::operation_aborted) {
155
+ ec2 = errc::common::unambiguous_timeout;
156
+ }
157
+ return self->handler_({ ec2 });
158
+ }
159
+ asio::async_read(self->tcp_,
160
+ asio::buffer(&self->recv_buf_size_, sizeof(std::uint16_t)),
161
+ [self](std::error_code ec3, std::size_t /* bytes_transferred */) mutable {
162
+ if (ec3) {
163
+ CB_LOG_DEBUG("DNS TCP buf size read operation has been aborted, {}", ec3.message());
164
+ self->deadline_.cancel();
165
+ return self->handler_({ ec3 });
166
+ }
167
+ self->recv_buf_size_ = utils::byte_swap(self->recv_buf_size_);
168
+ self->recv_buf_.resize(self->recv_buf_size_);
169
+ CB_LOG_DEBUG("DNS TCP schedule read of {} bytes", self->recv_buf_size_);
170
+ asio::async_read(self->tcp_,
171
+ asio::buffer(self->recv_buf_),
172
+ [self](std::error_code ec4, std::size_t bytes_transferred) mutable {
173
+ self->deadline_.cancel();
174
+ if (ec4) {
175
+ CB_LOG_DEBUG("DNS TCP read operation has been aborted, {}", ec4.message());
176
+ return self->handler_({ ec4 });
177
+ }
178
+ self->recv_buf_.resize(bytes_transferred);
179
+ const dns_message message = dns_codec::decode(self->recv_buf_);
180
+ dns_srv_response resp{ ec4 };
181
+ resp.targets.reserve(message.answers.size());
182
+ for (const auto& answer : message.answers) {
183
+ resp.targets.emplace_back(dns_srv_response::address{
184
+ utils::join_strings(answer.target.labels, "."), answer.port });
185
+ }
186
+ CB_LOG_DEBUG("DNS TCP returned {} records", resp.targets.size());
187
+ return self->handler_(std::move(resp));
188
+ });
189
+ });
190
+ });
191
+ });
192
+ }
193
+
194
+ asio::steady_timer deadline_;
195
+ asio::steady_timer udp_deadline_;
196
+ asio::ip::udp::socket udp_;
197
+ asio::ip::udp::endpoint udp_sender_{};
198
+ asio::ip::tcp::socket tcp_;
199
+
200
+ asio::ip::address address_;
201
+ std::uint16_t port_;
202
+ utils::movable_function<void(couchbase::core::io::dns::dns_srv_response&& resp)> handler_;
203
+
204
+ std::vector<std::uint8_t> send_buf_{};
205
+ std::uint16_t recv_buf_size_{ 0 };
206
+ std::vector<std::uint8_t> recv_buf_{};
207
+
208
+ std::atomic_bool retrying_with_tcp_{ false };
209
+ };
210
+
211
+ void
212
+ dns_client::query_srv(const std::string& name,
213
+ const std::string& service,
214
+ const dns_config& config,
215
+ utils::movable_function<void(dns_srv_response&&)>&& handler)
216
+ {
217
+ std::error_code ec;
218
+ auto address = asio::ip::address::from_string(config.nameserver(), ec);
219
+ if (ec) {
220
+ return handler({ ec });
221
+ }
222
+ auto cmd = std::make_shared<dns_srv_command>(ctx_, name, service, address, config.port(), std::move(handler));
223
+ return cmd->execute(config.timeout(), config.timeout() / 2);
224
+ }
225
+ } // namespace couchbase::core::io::dns