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.
- checksums.yaml +4 -4
- data/.github/workflows/tests-dev-preview.yml +52 -0
- data/.gitmodules +3 -0
- data/.idea/vcs.xml +1 -0
- data/.yardopts +1 -0
- data/README.md +1 -1
- data/Rakefile +5 -1
- data/bin/init-cluster +13 -5
- data/couchbase.gemspec +2 -1
- data/examples/managing_query_indexes.rb +1 -1
- data/examples/managing_search_indexes.rb +62 -0
- data/examples/search.rb +187 -0
- data/ext/.clang-tidy +1 -0
- data/ext/build_version.hxx.in +1 -1
- data/ext/couchbase/bucket.hxx +0 -40
- data/ext/couchbase/couchbase.cxx +2578 -1368
- data/ext/couchbase/io/http_session.hxx +27 -7
- data/ext/couchbase/io/mcbp_parser.hxx +2 -0
- data/ext/couchbase/io/mcbp_session.hxx +53 -24
- data/ext/couchbase/io/session_manager.hxx +6 -1
- data/ext/couchbase/operations.hxx +13 -0
- data/ext/couchbase/operations/bucket_create.hxx +1 -0
- data/ext/couchbase/operations/bucket_drop.hxx +1 -0
- data/ext/couchbase/operations/bucket_flush.hxx +1 -0
- data/ext/couchbase/operations/bucket_get.hxx +1 -0
- data/ext/couchbase/operations/bucket_get_all.hxx +1 -0
- data/ext/couchbase/operations/bucket_update.hxx +1 -0
- data/ext/couchbase/operations/cluster_developer_preview_enable.hxx +1 -0
- data/ext/couchbase/operations/collection_create.hxx +6 -1
- data/ext/couchbase/operations/collection_drop.hxx +1 -0
- data/ext/couchbase/operations/command.hxx +86 -11
- data/ext/couchbase/operations/document_decrement.hxx +1 -0
- data/ext/couchbase/operations/document_exists.hxx +1 -0
- data/ext/couchbase/operations/document_get.hxx +1 -0
- data/ext/couchbase/operations/document_get_and_lock.hxx +1 -0
- data/ext/couchbase/operations/document_get_and_touch.hxx +1 -0
- data/ext/couchbase/operations/document_get_projected.hxx +243 -0
- data/ext/couchbase/operations/document_increment.hxx +4 -1
- data/ext/couchbase/operations/document_insert.hxx +1 -0
- data/ext/couchbase/operations/document_lookup_in.hxx +1 -0
- data/ext/couchbase/operations/document_mutate_in.hxx +1 -0
- data/ext/couchbase/operations/document_query.hxx +13 -2
- data/ext/couchbase/operations/document_remove.hxx +1 -0
- data/ext/couchbase/operations/document_replace.hxx +1 -0
- data/ext/couchbase/operations/document_search.hxx +337 -0
- data/ext/couchbase/operations/document_touch.hxx +1 -0
- data/ext/couchbase/operations/document_unlock.hxx +1 -0
- data/ext/couchbase/operations/document_upsert.hxx +1 -0
- data/ext/couchbase/operations/query_index_build_deferred.hxx +1 -0
- data/ext/couchbase/operations/query_index_create.hxx +1 -0
- data/ext/couchbase/operations/query_index_drop.hxx +1 -0
- data/ext/couchbase/operations/query_index_get_all.hxx +1 -0
- data/ext/couchbase/operations/scope_create.hxx +1 -0
- data/ext/couchbase/operations/scope_drop.hxx +1 -0
- data/ext/couchbase/operations/scope_get_all.hxx +2 -0
- data/ext/couchbase/operations/search_index.hxx +62 -0
- data/ext/couchbase/operations/search_index_analyze_document.hxx +92 -0
- data/ext/couchbase/operations/search_index_control_ingest.hxx +78 -0
- data/ext/couchbase/operations/search_index_control_plan_freeze.hxx +80 -0
- data/ext/couchbase/operations/search_index_control_query.hxx +80 -0
- data/ext/couchbase/operations/search_index_drop.hxx +77 -0
- data/ext/couchbase/operations/search_index_get.hxx +80 -0
- data/ext/couchbase/operations/search_index_get_all.hxx +82 -0
- data/ext/couchbase/operations/search_index_get_documents_count.hxx +81 -0
- data/ext/couchbase/operations/search_index_upsert.hxx +106 -0
- data/ext/couchbase/protocol/client_opcode.hxx +10 -0
- data/ext/couchbase/protocol/cmd_get_collection_id.hxx +117 -0
- data/ext/couchbase/timeout_defaults.hxx +32 -0
- data/ext/couchbase/version.hxx +1 -1
- data/ext/test/main.cxx +5 -5
- data/lib/couchbase/binary_collection.rb +16 -12
- data/lib/couchbase/binary_collection_options.rb +4 -0
- data/lib/couchbase/cluster.rb +88 -8
- data/lib/couchbase/collection.rb +39 -15
- data/lib/couchbase/collection_options.rb +19 -2
- data/lib/couchbase/json_transcoder.rb +2 -2
- data/lib/couchbase/management/bucket_manager.rb +37 -23
- data/lib/couchbase/management/collection_manager.rb +15 -6
- data/lib/couchbase/management/query_index_manager.rb +16 -6
- data/lib/couchbase/management/search_index_manager.rb +61 -14
- data/lib/couchbase/search_options.rb +1492 -0
- data/lib/couchbase/version.rb +1 -1
- 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
|
{
|
@@ -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
|