couchbase 3.0.0.alpha.2 → 3.0.0.alpha.3

Sign up to get free protection for your applications and to get access to all the features.
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