couchbase 3.0.0.alpha.2-universal-darwin-19 → 3.0.0.alpha.3-universal-darwin-19

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/tests-dev-preview.yml +52 -0
  3. data/.gitmodules +3 -0
  4. data/.idea/vcs.xml +1 -0
  5. data/.yardopts +1 -0
  6. data/README.md +1 -1
  7. data/Rakefile +5 -1
  8. data/bin/init-cluster +13 -5
  9. data/couchbase.gemspec +2 -1
  10. data/examples/managing_query_indexes.rb +1 -1
  11. data/examples/managing_search_indexes.rb +62 -0
  12. data/examples/search.rb +187 -0
  13. data/ext/.clang-tidy +1 -0
  14. data/ext/build_version.hxx.in +1 -1
  15. data/ext/couchbase/bucket.hxx +0 -40
  16. data/ext/couchbase/couchbase.cxx +2578 -1368
  17. data/ext/couchbase/io/http_session.hxx +27 -7
  18. data/ext/couchbase/io/mcbp_parser.hxx +2 -0
  19. data/ext/couchbase/io/mcbp_session.hxx +53 -24
  20. data/ext/couchbase/io/session_manager.hxx +6 -1
  21. data/ext/couchbase/operations.hxx +13 -0
  22. data/ext/couchbase/operations/bucket_create.hxx +1 -0
  23. data/ext/couchbase/operations/bucket_drop.hxx +1 -0
  24. data/ext/couchbase/operations/bucket_flush.hxx +1 -0
  25. data/ext/couchbase/operations/bucket_get.hxx +1 -0
  26. data/ext/couchbase/operations/bucket_get_all.hxx +1 -0
  27. data/ext/couchbase/operations/bucket_update.hxx +1 -0
  28. data/ext/couchbase/operations/cluster_developer_preview_enable.hxx +1 -0
  29. data/ext/couchbase/operations/collection_create.hxx +6 -1
  30. data/ext/couchbase/operations/collection_drop.hxx +1 -0
  31. data/ext/couchbase/operations/command.hxx +86 -11
  32. data/ext/couchbase/operations/document_decrement.hxx +1 -0
  33. data/ext/couchbase/operations/document_exists.hxx +1 -0
  34. data/ext/couchbase/operations/document_get.hxx +1 -0
  35. data/ext/couchbase/operations/document_get_and_lock.hxx +1 -0
  36. data/ext/couchbase/operations/document_get_and_touch.hxx +1 -0
  37. data/ext/couchbase/operations/document_get_projected.hxx +243 -0
  38. data/ext/couchbase/operations/document_increment.hxx +4 -1
  39. data/ext/couchbase/operations/document_insert.hxx +1 -0
  40. data/ext/couchbase/operations/document_lookup_in.hxx +1 -0
  41. data/ext/couchbase/operations/document_mutate_in.hxx +1 -0
  42. data/ext/couchbase/operations/document_query.hxx +13 -2
  43. data/ext/couchbase/operations/document_remove.hxx +1 -0
  44. data/ext/couchbase/operations/document_replace.hxx +1 -0
  45. data/ext/couchbase/operations/document_search.hxx +337 -0
  46. data/ext/couchbase/operations/document_touch.hxx +1 -0
  47. data/ext/couchbase/operations/document_unlock.hxx +1 -0
  48. data/ext/couchbase/operations/document_upsert.hxx +1 -0
  49. data/ext/couchbase/operations/query_index_build_deferred.hxx +1 -0
  50. data/ext/couchbase/operations/query_index_create.hxx +1 -0
  51. data/ext/couchbase/operations/query_index_drop.hxx +1 -0
  52. data/ext/couchbase/operations/query_index_get_all.hxx +1 -0
  53. data/ext/couchbase/operations/scope_create.hxx +1 -0
  54. data/ext/couchbase/operations/scope_drop.hxx +1 -0
  55. data/ext/couchbase/operations/scope_get_all.hxx +2 -0
  56. data/ext/couchbase/operations/search_index.hxx +62 -0
  57. data/ext/couchbase/operations/search_index_analyze_document.hxx +92 -0
  58. data/ext/couchbase/operations/search_index_control_ingest.hxx +78 -0
  59. data/ext/couchbase/operations/search_index_control_plan_freeze.hxx +80 -0
  60. data/ext/couchbase/operations/search_index_control_query.hxx +80 -0
  61. data/ext/couchbase/operations/search_index_drop.hxx +77 -0
  62. data/ext/couchbase/operations/search_index_get.hxx +80 -0
  63. data/ext/couchbase/operations/search_index_get_all.hxx +82 -0
  64. data/ext/couchbase/operations/search_index_get_documents_count.hxx +81 -0
  65. data/ext/couchbase/operations/search_index_upsert.hxx +106 -0
  66. data/ext/couchbase/protocol/client_opcode.hxx +10 -0
  67. data/ext/couchbase/protocol/cmd_get_collection_id.hxx +117 -0
  68. data/ext/couchbase/timeout_defaults.hxx +32 -0
  69. data/ext/couchbase/version.hxx +1 -1
  70. data/ext/test/main.cxx +5 -5
  71. data/lib/couchbase/binary_collection.rb +16 -12
  72. data/lib/couchbase/binary_collection_options.rb +4 -0
  73. data/lib/couchbase/cluster.rb +88 -8
  74. data/lib/couchbase/collection.rb +39 -15
  75. data/lib/couchbase/collection_options.rb +19 -2
  76. data/lib/couchbase/json_transcoder.rb +2 -2
  77. data/lib/couchbase/management/bucket_manager.rb +37 -23
  78. data/lib/couchbase/management/collection_manager.rb +15 -6
  79. data/lib/couchbase/management/query_index_manager.rb +16 -6
  80. data/lib/couchbase/management/search_index_manager.rb +61 -14
  81. data/lib/couchbase/search_options.rb +1492 -0
  82. data/lib/couchbase/version.rb +1 -1
  83. metadata +22 -2
@@ -43,6 +43,7 @@ struct decrement_request {
43
43
  std::optional<std::uint64_t> initial_value{};
44
44
  protocol::durability_level durability_level{ protocol::durability_level::none };
45
45
  std::optional<std::uint16_t> durability_timeout{};
46
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
46
47
 
47
48
  void encode_to(encoded_request_type& encoded)
48
49
  {
@@ -40,6 +40,7 @@ struct exists_request {
40
40
  document_id id;
41
41
  std::uint16_t partition{};
42
42
  std::uint32_t opaque{};
43
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
43
44
 
44
45
  void encode_to(encoded_request_type& encoded)
45
46
  {
@@ -38,6 +38,7 @@ struct get_request {
38
38
  document_id id;
39
39
  uint16_t partition{};
40
40
  uint32_t opaque{};
41
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
41
42
 
42
43
  void encode_to(encoded_request_type& encoded)
43
44
  {
@@ -39,6 +39,7 @@ struct get_and_lock_request {
39
39
  uint16_t partition{};
40
40
  uint32_t opaque{};
41
41
  uint32_t lock_time{};
42
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
42
43
 
43
44
  void encode_to(encoded_request_type& encoded)
44
45
  {
@@ -39,6 +39,7 @@ struct get_and_touch_request {
39
39
  uint16_t partition{};
40
40
  uint32_t opaque{};
41
41
  uint32_t expiration{};
42
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
42
43
 
43
44
  void encode_to(encoded_request_type& encoded)
44
45
  {
@@ -0,0 +1,243 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2020 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 <document_id.hxx>
21
+ #include <protocol/cmd_lookup_in.hxx>
22
+
23
+ namespace couchbase::operations
24
+ {
25
+
26
+ struct get_projected_response {
27
+ document_id id;
28
+ std::error_code ec{};
29
+ std::string value{};
30
+ std::uint64_t cas{};
31
+ std::uint32_t flags{};
32
+ std::optional<std::uint32_t> expiration{};
33
+ };
34
+
35
+ struct get_projected_request {
36
+ using encoded_request_type = protocol::client_request<protocol::lookup_in_request_body>;
37
+ using encoded_response_type = protocol::client_response<protocol::lookup_in_response_body>;
38
+
39
+ document_id id;
40
+ std::uint16_t partition{};
41
+ std::uint32_t opaque{};
42
+ std::vector<std::string> projections{};
43
+ bool with_expiration{ false };
44
+ std::vector<std::string> effective_projections{};
45
+ bool preserve_array_indexes{ false };
46
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
47
+
48
+ void encode_to(encoded_request_type& encoded)
49
+ {
50
+ encoded.opaque(opaque);
51
+ encoded.partition(partition);
52
+ encoded.body().id(id);
53
+
54
+ effective_projections = projections;
55
+ std::size_t num_projections = effective_projections.size();
56
+ if (with_expiration) {
57
+ num_projections++;
58
+ }
59
+ if (num_projections > 16) {
60
+ // too many subdoc operations, better fetch full document
61
+ effective_projections.clear();
62
+ }
63
+
64
+ protocol::lookup_in_request_body::lookup_in_specs specs{};
65
+ if (with_expiration) {
66
+ specs.add_spec(protocol::subdoc_opcode::get, true, "$document.exptime");
67
+ }
68
+ if (effective_projections.empty()) {
69
+ specs.add_spec(protocol::subdoc_opcode::get_doc, false, "");
70
+ } else {
71
+ for (const auto& path : effective_projections) {
72
+ specs.add_spec(protocol::subdoc_opcode::get, false, path);
73
+ }
74
+ }
75
+ encoded.body().specs(specs);
76
+ }
77
+ };
78
+
79
+ namespace priv
80
+ {
81
+
82
+ std::optional<tao::json::value>
83
+ subdoc_lookup(tao::json::value& root, const std::string& path)
84
+ {
85
+ std::string::size_type offset = 0;
86
+ tao::json::value* cur = &root;
87
+
88
+ while (offset < path.size()) {
89
+ std::string::size_type idx = path.find_first_of(".[]", offset);
90
+
91
+ if (idx == std::string::npos) {
92
+ std::string key = path.substr(offset);
93
+ auto* val = cur->find(key);
94
+ if (val != nullptr) {
95
+ return *val;
96
+ }
97
+ break;
98
+ }
99
+
100
+ if (path[idx] == '.' || path[idx] == '[') {
101
+ std::string key = path.substr(offset, idx - offset);
102
+ auto* val = cur->find(key);
103
+ if (val == nullptr) {
104
+ break;
105
+ }
106
+ cur = val;
107
+ } else if (path[idx] == ']') {
108
+ if (!cur->is_array()) {
109
+ break;
110
+ }
111
+ std::string key = path.substr(offset, idx - offset);
112
+ int array_index = std::stoi(key);
113
+ if (array_index == -1) {
114
+ cur = &cur->get_array().back();
115
+ } else if (static_cast<std::size_t>(array_index) < cur->get_array().size()) {
116
+ cur = &cur->get_array().back();
117
+ } else {
118
+ break;
119
+ }
120
+ if (idx < path.size() - 1) {
121
+ return *cur;
122
+ }
123
+ idx += 1;
124
+ }
125
+ offset = idx + 1;
126
+ }
127
+
128
+ return {};
129
+ }
130
+
131
+ void
132
+ subdoc_apply_projection(tao::json::value& root, const std::string& path, tao::json::value& value, bool preserve_array_indexes)
133
+ {
134
+ std::string::size_type offset = 0;
135
+ tao::json::value* cur = &root;
136
+
137
+ while (offset < path.size()) {
138
+ std::string::size_type idx = path.find_first_of(".[]", offset);
139
+
140
+ if (idx == std::string::npos) {
141
+ cur->operator[](path.substr(offset)) = value;
142
+ break;
143
+ }
144
+
145
+ if (path[idx] == '.') {
146
+ std::string key = path.substr(offset, idx - offset);
147
+ tao::json::value* child = cur->find(key);
148
+ if (child == nullptr) {
149
+ cur->operator[](key) = tao::json::empty_object;
150
+ child = cur->find(key);
151
+ }
152
+ cur = child;
153
+ } else if (path[idx] == '[') {
154
+ std::string key = path.substr(offset, idx - offset);
155
+ tao::json::value* child = cur->find(key);
156
+ if (child == nullptr) {
157
+ cur->operator[](key) = tao::json::empty_array;
158
+ child = cur->find(key);
159
+ }
160
+ cur = child;
161
+ } else if (path[idx] == ']') {
162
+ tao::json::value child;
163
+ if (idx == path.size() - 1) {
164
+ child = value;
165
+ } else if (path[idx + 1] == '.') {
166
+ child = tao::json::empty_object;
167
+ } else if (path[idx + 1] == '[') {
168
+ child = tao::json::empty_array;
169
+ } else {
170
+ Expects(false);
171
+ }
172
+ if (preserve_array_indexes) {
173
+ int array_index = std::stoi(path.substr(offset, idx - offset));
174
+ if (array_index >= 0) {
175
+ if (static_cast<std::size_t>(array_index) >= cur->get_array().size()) {
176
+ cur->get_array().resize(static_cast<std::size_t>(array_index + 1), tao::json::null);
177
+ }
178
+ cur->at(static_cast<std::size_t>(array_index)) = child;
179
+ cur = &cur->at(static_cast<std::size_t>(array_index));
180
+ } else {
181
+ // index is negative, just append and let user decide what it means
182
+ cur->get_array().push_back(child);
183
+ cur = &cur->get_array().back();
184
+ }
185
+ } else {
186
+ cur->get_array().push_back(child);
187
+ cur = &cur->get_array().back();
188
+ }
189
+ ++idx;
190
+ }
191
+ offset = idx + 1;
192
+ }
193
+ }
194
+ } // namespace priv
195
+
196
+ get_projected_response
197
+ make_response(std::error_code ec, get_projected_request& request, get_projected_request::encoded_response_type encoded)
198
+ {
199
+ get_projected_response response{ request.id, ec };
200
+ if (!ec) {
201
+ response.cas = encoded.cas();
202
+ if (request.with_expiration) {
203
+ response.expiration = gsl::narrow_cast<std::uint32_t>(std::stoul(encoded.body().fields()[0].value));
204
+ }
205
+ if (request.effective_projections.empty()) {
206
+ // from full document
207
+ if (request.projections.empty() && request.with_expiration) {
208
+ // special case when user only wanted full+expiration
209
+ response.value = encoded.body().fields()[1].value;
210
+ } else {
211
+ tao::json::value full_doc = tao::json::from_string(encoded.body().fields()[request.with_expiration ? 1 : 0].value);
212
+ tao::json::value new_doc;
213
+ for (const auto& projection : request.projections) {
214
+ auto value_to_apply = priv::subdoc_lookup(full_doc, projection);
215
+ if (value_to_apply) {
216
+ priv::subdoc_apply_projection(new_doc, projection, *value_to_apply, request.preserve_array_indexes);
217
+ } else {
218
+ response.ec = std::make_error_code(error::key_value_errc::path_not_found);
219
+ return response;
220
+ }
221
+ }
222
+ response.value = tao::json::to_string(new_doc);
223
+ }
224
+ } else {
225
+ tao::json::value new_doc = tao::json::empty_object;
226
+ std::size_t offset = request.with_expiration ? 1 : 0;
227
+ for (const auto& projection : request.projections) {
228
+ auto& field = encoded.body().fields()[offset++];
229
+ if (field.status == protocol::status::success && !field.value.empty()) {
230
+ auto value_to_apply = tao::json::from_string(field.value);
231
+ priv::subdoc_apply_projection(new_doc, projection, value_to_apply, request.preserve_array_indexes);
232
+ } else {
233
+ response.ec = std::make_error_code(error::key_value_errc::path_not_found);
234
+ return response;
235
+ }
236
+ }
237
+ response.value = tao::json::to_string(new_doc);
238
+ }
239
+ }
240
+ return response;
241
+ }
242
+
243
+ } // namespace couchbase::operations
@@ -17,8 +17,10 @@
17
17
 
18
18
  #pragma once
19
19
 
20
- #include <document_id.hxx>
21
20
  #include <protocol/cmd_increment.hxx>
21
+ #include <protocol/durability_level.hxx>
22
+ #include <operations.hxx>
23
+ #include <protocol/client_response.hxx>
22
24
 
23
25
  namespace couchbase::operations
24
26
  {
@@ -43,6 +45,7 @@ struct increment_request {
43
45
  std::optional<std::uint64_t> initial_value{};
44
46
  protocol::durability_level durability_level{ protocol::durability_level::none };
45
47
  std::optional<std::uint16_t> durability_timeout{};
48
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
46
49
 
47
50
  void encode_to(encoded_request_type& encoded)
48
51
  {
@@ -43,6 +43,7 @@ struct insert_request {
43
43
  uint32_t expiration{ 0 };
44
44
  protocol::durability_level durability_level{ protocol::durability_level::none };
45
45
  std::optional<std::uint16_t> durability_timeout{};
46
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
46
47
 
47
48
  void encode_to(encoded_request_type& encoded)
48
49
  {
@@ -47,6 +47,7 @@ struct lookup_in_request {
47
47
  uint32_t opaque{};
48
48
  bool access_deleted{ false };
49
49
  protocol::lookup_in_request_body::lookup_in_specs specs{};
50
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
50
51
 
51
52
  void encode_to(encoded_request_type& encoded)
52
53
  {
@@ -50,6 +50,7 @@ struct mutate_in_request {
50
50
  protocol::mutate_in_request_body::mutate_in_specs specs{};
51
51
  protocol::durability_level durability_level{ protocol::durability_level::none };
52
52
  std::optional<std::uint16_t> durability_timeout{};
53
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
53
54
 
54
55
  void encode_to(encoded_request_type& encoded)
55
56
  {
@@ -17,10 +17,21 @@
17
17
 
18
18
  #pragma once
19
19
 
20
+ #include <gsl/gsl>
21
+
22
+ #include <spdlog/spdlog.h>
23
+
20
24
  #include <tao/json.hpp>
21
25
 
22
26
  #include <version.hxx>
23
27
 
28
+ #include <errors.hxx>
29
+ #include <mutation_token.hxx>
30
+ #include <service_type.hxx>
31
+ #include <platform/uuid.h>
32
+ #include <timeout_defaults.hxx>
33
+ #include <io/http_message.hxx>
34
+
24
35
  namespace couchbase::operations
25
36
  {
26
37
  struct query_response_payload {
@@ -142,7 +153,6 @@ struct query_request {
142
153
 
143
154
  static const inline service_type type = service_type::query;
144
155
 
145
- std::uint64_t timeout{ 75'000 }; // milliseconds
146
156
  std::string statement;
147
157
  std::string client_context_id{ uuid::to_string(uuid::random()) };
148
158
 
@@ -157,6 +167,7 @@ struct query_request {
157
167
  std::optional<std::uint64_t> pipeline_cap{};
158
168
  std::optional<scan_consistency_type> scan_consistency{};
159
169
  std::vector<mutation_token> mutation_state{};
170
+ std::chrono::milliseconds timeout{ timeout_defaults::query_timeout };
160
171
 
161
172
  enum class profile_mode {
162
173
  off,
@@ -174,7 +185,7 @@ struct query_request {
174
185
  encoded.headers["content-type"] = "application/json";
175
186
  tao::json::value body{ { "statement", statement },
176
187
  { "client_context_id", client_context_id },
177
- { "timeout", fmt::format("{}ms", timeout) } };
188
+ { "timeout", fmt::format("{}ms", timeout.count()) } };
178
189
  if (positional_parameters.empty()) {
179
190
  for (auto& param : named_parameters) {
180
191
  Expects(param.first.empty() == false);
@@ -39,6 +39,7 @@ struct remove_request {
39
39
  uint32_t opaque{};
40
40
  protocol::durability_level durability_level{ protocol::durability_level::none };
41
41
  std::optional<std::uint16_t> durability_timeout{};
42
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
42
43
 
43
44
  void encode_to(encoded_request_type& encoded)
44
45
  {
@@ -44,6 +44,7 @@ struct replace_request {
44
44
  uint64_t cas{ 0 };
45
45
  protocol::durability_level durability_level{ protocol::durability_level::none };
46
46
  std::optional<std::uint16_t> durability_timeout{};
47
+ std::chrono::milliseconds timeout{ timeout_defaults::key_value_timeout };
47
48
 
48
49
  void encode_to(encoded_request_type& encoded)
49
50
  {
@@ -0,0 +1,337 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2020 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 <tao/json.hpp>
21
+
22
+ #include <version.hxx>
23
+
24
+ namespace couchbase::operations
25
+ {
26
+ struct search_response {
27
+ struct search_metrics {
28
+ std::chrono::nanoseconds took;
29
+ std::uint64_t total_rows;
30
+ double max_score;
31
+ std::uint64_t success_partition_count;
32
+ std::uint64_t error_partition_count;
33
+ };
34
+
35
+ struct search_meta_data {
36
+ std::string client_context_id;
37
+ search_metrics metrics;
38
+ std::map<std::string, std::string> errors;
39
+ };
40
+
41
+ struct search_location {
42
+ std::string field;
43
+ std::string term;
44
+ std::uint64_t position;
45
+ std::uint64_t start_offset;
46
+ std::uint64_t end_offset;
47
+ std::optional<std::vector<std::uint64_t>> array_positions{};
48
+ };
49
+
50
+ struct search_row {
51
+ std::string index;
52
+ std::string id;
53
+ double score;
54
+ std::vector<search_location> locations{};
55
+ std::map<std::string, std::vector<std::string>> fragments{};
56
+ std::string fields{};
57
+ std::string explanation{};
58
+ };
59
+
60
+ struct search_facet {
61
+ struct term_facet {
62
+ std::string term;
63
+ std::uint64_t count;
64
+ };
65
+
66
+ struct date_range_facet {
67
+ std::string name;
68
+ std::uint64_t count;
69
+ std::optional<std::string> start{};
70
+ std::optional<std::string> end{};
71
+ };
72
+
73
+ struct numeric_range_facet {
74
+ std::string name;
75
+ std::uint64_t count;
76
+ std::variant<std::monostate, std::uint64_t, double> min{};
77
+ std::variant<std::monostate, std::uint64_t, double> max{};
78
+ };
79
+
80
+ std::string name;
81
+ std::string field;
82
+ std::uint64_t total;
83
+ std::uint64_t missing;
84
+ std::uint64_t other;
85
+ std::vector<term_facet> terms{};
86
+ std::vector<date_range_facet> date_ranges{};
87
+ std::vector<numeric_range_facet> numeric_ranges{};
88
+ };
89
+
90
+ std::error_code ec;
91
+ std::string status{};
92
+ search_meta_data meta_data{};
93
+ std::string error{};
94
+ std::vector<search_row> rows{};
95
+ std::vector<search_facet> facets{};
96
+ };
97
+
98
+ struct search_request {
99
+ using response_type = search_response;
100
+ using encoded_request_type = io::http_request;
101
+ using encoded_response_type = io::http_response;
102
+
103
+ static const inline service_type type = service_type::search;
104
+
105
+ std::string client_context_id{ uuid::to_string(uuid::random()) };
106
+ std::chrono::milliseconds timeout{ timeout_defaults::management_timeout };
107
+
108
+ std::string index_name;
109
+ tao::json::value query;
110
+
111
+ std::optional<std::uint32_t> limit{};
112
+ std::optional<std::uint32_t> skip{};
113
+ bool explain{ false };
114
+
115
+ enum class highlight_style_type { html, ansi };
116
+ std::optional<highlight_style_type> highlight_style{};
117
+ std::vector<std::string> highlight_fields{};
118
+ std::vector<std::string> fields{};
119
+
120
+ enum class scan_consistency_type { not_bounded };
121
+ std::optional<scan_consistency_type> scan_consistency{};
122
+ std::vector<mutation_token> mutation_state{};
123
+
124
+ std::vector<std::string> sort_specs{};
125
+
126
+ std::map<std::string, std::string> facets{};
127
+
128
+ std::map<std::string, tao::json::value> raw{};
129
+
130
+ void encode_to(encoded_request_type& encoded)
131
+ {
132
+ encoded.headers["content-type"] = "application/json";
133
+ encoded.headers["client-context-id"] = client_context_id;
134
+ tao::json::value body{ { "query", query }, { "explain", explain }, { "timeout", fmt::format("{}ms", timeout.count()) } };
135
+ if (limit) {
136
+ body["size"] = *limit;
137
+ }
138
+ if (skip) {
139
+ body["from"] = *skip;
140
+ }
141
+ if (highlight_style || !highlight_fields.empty()) {
142
+ tao::json::value highlight;
143
+ if (highlight_style) {
144
+ switch (*highlight_style) {
145
+ case highlight_style_type::html:
146
+ highlight["style"] = "html";
147
+ break;
148
+ case highlight_style_type::ansi:
149
+ highlight["style"] = "ansi";
150
+ break;
151
+ }
152
+ }
153
+ if (!highlight_fields.empty()) {
154
+ highlight["fields"] = highlight_fields;
155
+ }
156
+ body["highlight"] = highlight;
157
+ }
158
+ if (!fields.empty()) {
159
+ body["fields"] = fields;
160
+ }
161
+ if (!sort_specs.empty()) {
162
+ body["sort"] = tao::json::empty_array;
163
+ for (const auto& spec : sort_specs) {
164
+ body["sort"].get_array().push_back(tao::json::from_string(spec));
165
+ }
166
+ }
167
+ if (!facets.empty()) {
168
+ body["facets"] = tao::json::empty_object;
169
+ for (const auto& facet : facets) {
170
+ body["facets"][facet.first] = tao::json::from_string(facet.second);
171
+ }
172
+ }
173
+
174
+ encoded.method = "POST";
175
+ encoded.path = fmt::format("/api/index/{}/query", index_name);
176
+ encoded.body = tao::json::to_string(body);
177
+ }
178
+ };
179
+
180
+ search_response
181
+ make_response(std::error_code ec, search_request& request, search_request::encoded_response_type encoded)
182
+ {
183
+ search_response response{ ec };
184
+ response.meta_data.client_context_id = request.client_context_id;
185
+ if (!ec) {
186
+ if (encoded.status_code == 200) {
187
+ auto payload = tao::json::from_string(encoded.body);
188
+ response.meta_data.metrics.took = std::chrono::nanoseconds(payload.at("took").get_unsigned());
189
+ response.meta_data.metrics.max_score = payload.at("max_score").as<double>();
190
+ response.meta_data.metrics.total_rows = payload.at("total_hits").get_unsigned();
191
+ auto status_prop = payload.at("status");
192
+ if (status_prop.is_string()) {
193
+ response.status = status_prop.get_string();
194
+ if (response.status == "ok") {
195
+ return response;
196
+ }
197
+ } else if (status_prop.is_object()) {
198
+ response.meta_data.metrics.error_partition_count = status_prop.at("failed").get_unsigned();
199
+ response.meta_data.metrics.success_partition_count = status_prop.at("successful").get_unsigned();
200
+ const auto* errors = status_prop.find("errors");
201
+ if (errors != nullptr && errors->is_object()) {
202
+ for (const auto& error : errors->get_object()) {
203
+ response.meta_data.errors.emplace(error.first, error.second.get_string());
204
+ }
205
+ }
206
+ } else {
207
+ response.ec = std::make_error_code(error::common_errc::internal_server_failure);
208
+ return response;
209
+ }
210
+ const auto* rows = payload.find("hits");
211
+ if (rows != nullptr && rows->is_array()) {
212
+ for (const auto& entry : rows->get_array()) {
213
+ search_response::search_row row{};
214
+ row.index = entry.at("index").get_string();
215
+ row.id = entry.at("id").get_string();
216
+ row.score = entry.at("score").get_double();
217
+ const auto* locations = entry.find("locations");
218
+ if (locations != nullptr && locations->is_object()) {
219
+ for (const auto& field : locations->get_object()) {
220
+ for (const auto& term : field.second.get_object()) {
221
+ for (const auto& loc : term.second.get_array()) {
222
+ search_response::search_location location{};
223
+ location.field = field.first;
224
+ location.term = term.first;
225
+ location.position = loc.at("pos").get_unsigned();
226
+ location.start_offset = loc.at("start").get_unsigned();
227
+ location.end_offset = loc.at("end").get_unsigned();
228
+ const auto* ap = loc.find("array_positions");
229
+ if (ap != nullptr && ap->is_array()) {
230
+ location.array_positions.emplace(ap->as<std::vector<std::uint64_t>>());
231
+ }
232
+ row.locations.emplace_back(location);
233
+ }
234
+ }
235
+ }
236
+ }
237
+ const auto* fragments_map = entry.find("fragments");
238
+ if (fragments_map != nullptr && fragments_map->is_object()) {
239
+ for (const auto& field : fragments_map->get_object()) {
240
+ row.fragments.emplace(field.first, field.second.as<std::vector<std::string>>());
241
+ }
242
+ }
243
+ const auto* fields = entry.find("fields");
244
+ if (fields != nullptr && fields->is_object()) {
245
+ row.fields = tao::json::to_string(*fields);
246
+ }
247
+ const auto* explanation = entry.find("explanation");
248
+ if (explanation != nullptr && explanation->is_object()) {
249
+ row.explanation = tao::json::to_string(*explanation);
250
+ }
251
+ response.rows.emplace_back(row);
252
+ }
253
+ }
254
+ const auto* facets = payload.find("facets");
255
+ if (facets != nullptr && facets->is_object()) {
256
+ for (const auto& entry : facets->get_object()) {
257
+ search_response::search_facet facet;
258
+ facet.name = entry.first;
259
+ facet.field = entry.second.at("field").get_string();
260
+ facet.total = entry.second.at("total").get_unsigned();
261
+ facet.missing = entry.second.at("missing").get_unsigned();
262
+ facet.other = entry.second.at("other").get_unsigned();
263
+
264
+ const auto& date_ranges = entry.second.find("date_ranges");
265
+ if (date_ranges != nullptr && date_ranges->is_array()) {
266
+ for (const auto& date_range : date_ranges->get_array()) {
267
+ search_response::search_facet::date_range_facet drf;
268
+ drf.name = date_range.at("name").get_string();
269
+ drf.count = date_range.at("count").get_unsigned();
270
+ const auto* start = date_range.find("start");
271
+ if (start != nullptr && start->is_string()) {
272
+ drf.start = start->get_string();
273
+ }
274
+ const auto* end = date_range.find("end");
275
+ if (end != nullptr && end->is_string()) {
276
+ drf.end = end->get_string();
277
+ }
278
+ facet.date_ranges.emplace_back(drf);
279
+ }
280
+ }
281
+
282
+ const auto& numeric_ranges = entry.second.find("numeric_ranges");
283
+ if (numeric_ranges != nullptr && numeric_ranges->is_array()) {
284
+ for (const auto& numeric_range : numeric_ranges->get_array()) {
285
+ search_response::search_facet::numeric_range_facet nrf;
286
+ nrf.name = numeric_range.at("name").get_string();
287
+ nrf.count = numeric_range.at("count").get_unsigned();
288
+ const auto* min = numeric_range.find("min");
289
+ if (min != nullptr) {
290
+ if (min->is_double()) {
291
+ nrf.min = min->get_double();
292
+ } else if (min->is_integer()) {
293
+ nrf.min = min->get_unsigned();
294
+ }
295
+ }
296
+ const auto* max = numeric_range.find("max");
297
+ if (max != nullptr) {
298
+ if (max->is_double()) {
299
+ nrf.max = max->get_double();
300
+ } else if (max->is_integer()) {
301
+ nrf.max = max->get_unsigned();
302
+ }
303
+ }
304
+ facet.numeric_ranges.emplace_back(nrf);
305
+ }
306
+ }
307
+
308
+ const auto& terms = entry.second.find("terms");
309
+ if (terms != nullptr && terms->is_array()) {
310
+ for (const auto& term : terms->get_array()) {
311
+ search_response::search_facet::term_facet tf;
312
+ tf.term = term.at("term").get_string();
313
+ tf.count = term.at("count").get_unsigned();
314
+ facet.terms.emplace_back(tf);
315
+ }
316
+ }
317
+
318
+ response.facets.emplace_back(facet);
319
+ }
320
+ }
321
+ return response;
322
+ }
323
+ if (encoded.status_code == 400) {
324
+ auto payload = tao::json::from_string(encoded.body);
325
+ response.status = payload.at("status").get_string();
326
+ response.error = payload.at("error").get_string();
327
+ if (response.error.find("index not found") != std::string::npos) {
328
+ response.ec = std::make_error_code(error::common_errc::index_not_found);
329
+ return response;
330
+ }
331
+ }
332
+ response.ec = std::make_error_code(error::common_errc::internal_server_failure);
333
+ }
334
+ return response;
335
+ }
336
+
337
+ } // namespace couchbase::operations