couchbase 3.4.1 → 3.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/couchbase/CMakeLists.txt +2 -0
  4. data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +4 -0
  5. data/ext/couchbase/core/cluster_options.hxx +0 -1
  6. data/ext/couchbase/core/config_profile.cxx +23 -1
  7. data/ext/couchbase/core/config_profile.hxx +2 -12
  8. data/ext/couchbase/core/impl/analytics.cxx +236 -0
  9. data/ext/couchbase/core/impl/cluster.cxx +0 -1
  10. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +5 -3
  11. data/ext/couchbase/core/impl/query.cxx +5 -5
  12. data/ext/couchbase/core/io/dns_client.cxx +225 -0
  13. data/ext/couchbase/core/io/dns_client.hxx +19 -188
  14. data/ext/couchbase/core/transactions/active_transaction_record.hxx +2 -2
  15. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +3 -0
  16. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +1 -1
  17. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +12 -12
  18. data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +7 -1
  19. data/ext/couchbase/core/transactions/transaction_context.cxx +1 -0
  20. data/ext/couchbase/core/transactions/transactions_cleanup.cxx +144 -155
  21. data/ext/couchbase/core/utils/connection_string.cxx +10 -3
  22. data/ext/couchbase/core/utils/connection_string.hxx +3 -3
  23. data/ext/couchbase/couchbase/analytics_error_context.hxx +143 -0
  24. data/ext/couchbase/couchbase/analytics_meta_data.hxx +155 -0
  25. data/ext/couchbase/couchbase/analytics_metrics.hxx +163 -0
  26. data/ext/couchbase/couchbase/analytics_options.hxx +359 -0
  27. data/ext/couchbase/couchbase/analytics_result.hxx +102 -0
  28. data/ext/couchbase/couchbase/analytics_scan_consistency.hxx +46 -0
  29. data/ext/couchbase/couchbase/analytics_status.hxx +41 -0
  30. data/ext/couchbase/couchbase/analytics_warning.hxx +85 -0
  31. data/ext/couchbase/couchbase/cluster.hxx +33 -0
  32. data/ext/couchbase/couchbase/fmt/analytics_status.hxx +76 -0
  33. data/ext/couchbase/couchbase/query_options.hxx +0 -1
  34. data/ext/couchbase/couchbase/scope.hxx +33 -0
  35. data/ext/couchbase/couchbase/transactions/attempt_context.hxx +1 -1
  36. data/ext/couchbase/test/CMakeLists.txt +1 -2
  37. data/ext/couchbase/test/test_helper.hxx +1 -1
  38. data/ext/couchbase/test/test_integration_analytics.cxx +289 -13
  39. data/ext/couchbase/test/test_integration_crud.cxx +8 -1
  40. data/ext/couchbase/test/test_integration_examples.cxx +41 -0
  41. data/ext/couchbase/test/test_integration_management.cxx +15 -3
  42. data/ext/couchbase/test/test_integration_search.cxx +601 -0
  43. data/ext/couchbase/test/test_transaction_transaction_simple.cxx +73 -0
  44. data/ext/couchbase/test/test_unit_config_profiles.cxx +12 -12
  45. data/ext/couchbase/test/test_unit_connection_string.cxx +35 -0
  46. data/ext/couchbase/third_party/snappy/CMakeLists.txt +150 -27
  47. data/ext/couchbase/third_party/snappy/cmake/config.h.in +28 -24
  48. data/ext/couchbase/third_party/snappy/snappy-internal.h +189 -25
  49. data/ext/couchbase/third_party/snappy/snappy-sinksource.cc +26 -9
  50. data/ext/couchbase/third_party/snappy/snappy-sinksource.h +11 -11
  51. data/ext/couchbase/third_party/snappy/snappy-stubs-internal.cc +1 -1
  52. data/ext/couchbase/third_party/snappy/snappy-stubs-internal.h +227 -308
  53. data/ext/couchbase/third_party/snappy/snappy-stubs-public.h.in +0 -11
  54. data/ext/couchbase/third_party/snappy/snappy.cc +1176 -410
  55. data/ext/couchbase/third_party/snappy/snappy.h +19 -4
  56. data/ext/couchbase.cxx +27 -6
  57. data/ext/revisions.rb +3 -3
  58. data/lib/couchbase/cluster.rb +13 -9
  59. data/lib/couchbase/cluster_registry.rb +7 -2
  60. data/lib/couchbase/configuration.rb +3 -4
  61. data/lib/couchbase/options.rb +85 -2
  62. data/lib/couchbase/search_options.rb +158 -240
  63. data/lib/couchbase/version.rb +1 -1
  64. metadata +17 -6
  65. data/ext/couchbase/core/CMakeLists.txt +0 -0
@@ -0,0 +1,601 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2020-2021 Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #include "test_helper_integration.hxx"
19
+
20
+ #include "core/operations/management/collection_create.hxx"
21
+ #include "core/operations/management/search_index_drop.hxx"
22
+ #include "core/operations/management/search_index_upsert.hxx"
23
+
24
+ using Catch::Matchers::StartsWith;
25
+
26
+ TEST_CASE("integration: search query")
27
+ {
28
+ test::utils::integration_test_guard integration;
29
+
30
+ test::utils::open_bucket(integration.cluster, integration.ctx.bucket);
31
+
32
+ {
33
+ auto sample_data =
34
+ couchbase::core::utils::json::parse(couchbase::core::json_string(test::utils::read_test_data("search_beers_dataset.json")));
35
+ auto const& o = sample_data.get_object();
36
+ for (const auto& [key, value] : o) {
37
+ couchbase::core::document_id id(integration.ctx.bucket, "_default", "_default", key);
38
+ couchbase::core::operations::upsert_request req{ id, couchbase::core::utils::json::generate_binary(value) };
39
+ auto resp = test::utils::execute(integration.cluster, req);
40
+ REQUIRE_SUCCESS(resp.ctx.ec());
41
+ }
42
+ }
43
+
44
+ std::string index_name = test::utils::uniq_id("beer-search-index");
45
+
46
+ {
47
+ auto params = test::utils::read_test_data("search_beers_index_params.json");
48
+
49
+ couchbase::core::management::search::index index{};
50
+ index.name = index_name;
51
+ index.params_json = params;
52
+ index.type = "fulltext-index";
53
+ index.source_name = integration.ctx.bucket;
54
+ index.source_type = "couchbase";
55
+ if (integration.cluster_version().requires_search_replicas()) {
56
+ index.plan_params_json = couchbase::core::utils::json::generate({
57
+ { "indexPartitions", 1 },
58
+ { "numReplicas", 1 },
59
+ });
60
+ }
61
+ couchbase::core::operations::management::search_index_upsert_request req{};
62
+ req.index = index;
63
+
64
+ auto resp = test::utils::execute(integration.cluster, req);
65
+ REQUIRE((!resp.ctx.ec || resp.ctx.ec == couchbase::errc::common::index_exists));
66
+ if (index_name != resp.name) {
67
+ CB_LOG_INFO("update index name \"{}\" -> \"{}\"", index_name, resp.name);
68
+ }
69
+ index_name = resp.name;
70
+ }
71
+
72
+ couchbase::core::json_string simple_query(R"({"query": "description:belgian"})");
73
+
74
+ std::uint64_t beer_sample_doc_count = 5;
75
+ // Wait until expected documents are indexed
76
+ {
77
+ REQUIRE(test::utils::wait_until_indexed(integration.cluster, index_name, beer_sample_doc_count));
78
+ auto ok = test::utils::wait_until(
79
+ [&]() {
80
+ couchbase::core::operations::search_request req{};
81
+ req.index_name = index_name;
82
+ req.query = simple_query;
83
+ auto resp = test::utils::execute(integration.cluster, req);
84
+ REQUIRE_SUCCESS(resp.ctx.ec);
85
+ return resp.rows.size() == beer_sample_doc_count;
86
+ },
87
+ std::chrono::minutes(5));
88
+ REQUIRE(ok);
89
+ }
90
+
91
+ SECTION("simple query")
92
+ {
93
+ couchbase::core::operations::search_request req{};
94
+ req.index_name = index_name;
95
+ req.query = simple_query;
96
+ req.sort_specs.emplace_back(couchbase::core::utils::json::generate("_id"));
97
+ auto resp = test::utils::execute(integration.cluster, req);
98
+ REQUIRE_SUCCESS(resp.ctx.ec);
99
+ REQUIRE(resp.rows.size() == 5);
100
+ REQUIRE(resp.rows[0].id == "avery_brewing_company-reverend_the");
101
+ REQUIRE(resp.rows[0].score > 0);
102
+ REQUIRE_THAT(resp.rows[0].index, StartsWith(index_name));
103
+ REQUIRE(resp.rows[0].locations.empty());
104
+ REQUIRE(resp.rows[0].explanation.empty());
105
+ REQUIRE(resp.rows[0].fields.empty());
106
+ REQUIRE(resp.rows[0].fragments.empty());
107
+ REQUIRE(resp.meta.metrics.max_score > 0);
108
+ REQUIRE(resp.meta.metrics.total_rows == 5);
109
+ REQUIRE(resp.meta.metrics.took > std::chrono::nanoseconds(0));
110
+ }
111
+
112
+ SECTION("limit")
113
+ {
114
+ couchbase::core::operations::search_request req{};
115
+ req.index_name = index_name;
116
+ req.query = simple_query;
117
+ req.limit = 1;
118
+ auto resp = test::utils::execute(integration.cluster, req);
119
+ REQUIRE_SUCCESS(resp.ctx.ec);
120
+ REQUIRE(resp.rows.size() == 1);
121
+ }
122
+
123
+ SECTION("skip")
124
+ {
125
+ couchbase::core::operations::search_request req{};
126
+ req.index_name = index_name;
127
+ req.query = simple_query;
128
+ req.skip = 1;
129
+ req.sort_specs.emplace_back(couchbase::core::utils::json::generate("_id"));
130
+ auto resp = test::utils::execute(integration.cluster, req);
131
+ REQUIRE_SUCCESS(resp.ctx.ec);
132
+ REQUIRE(resp.rows.size() == beer_sample_doc_count - 1);
133
+ REQUIRE(resp.rows[0].id == "bear_republic_brewery-red_rocket_ale");
134
+ }
135
+
136
+ SECTION("explain")
137
+ {
138
+ couchbase::core::operations::search_request req{};
139
+ req.index_name = index_name;
140
+ req.query = simple_query;
141
+ req.explain = true;
142
+ auto resp = test::utils::execute(integration.cluster, req);
143
+ REQUIRE_SUCCESS(resp.ctx.ec);
144
+ REQUIRE_FALSE(resp.rows[0].explanation.empty());
145
+ }
146
+
147
+ if (integration.cluster_version().supports_search_disable_scoring()) {
148
+ SECTION("disable scoring")
149
+ {
150
+ couchbase::core::operations::search_request req{};
151
+ req.index_name = index_name;
152
+ req.query = simple_query;
153
+ req.disable_scoring = true;
154
+ auto resp = test::utils::execute(integration.cluster, req);
155
+ REQUIRE_SUCCESS(resp.ctx.ec);
156
+ REQUIRE(resp.rows[0].score == 0);
157
+ REQUIRE(resp.meta.metrics.max_score == 0);
158
+ }
159
+ }
160
+
161
+ SECTION("include locations")
162
+ {
163
+ couchbase::core::operations::search_request req{};
164
+ req.index_name = index_name;
165
+ req.query = simple_query;
166
+ req.sort_specs.emplace_back(couchbase::core::utils::json::generate("_id"));
167
+ req.include_locations = true;
168
+ auto resp = test::utils::execute(integration.cluster, req);
169
+ REQUIRE_SUCCESS(resp.ctx.ec);
170
+ REQUIRE(resp.rows[0].locations.size() == 1);
171
+ REQUIRE(resp.rows[0].locations[0].field == "description");
172
+ REQUIRE(resp.rows[0].locations[0].term == "belgian");
173
+ REQUIRE(resp.rows[0].locations[0].position == 1);
174
+ REQUIRE(resp.rows[0].locations[0].start_offset == 0);
175
+ REQUIRE(resp.rows[0].locations[0].end_offset == 7);
176
+ }
177
+
178
+ SECTION("highlight fields default highlight style")
179
+ {
180
+ couchbase::core::operations::search_request req{};
181
+ req.index_name = index_name;
182
+ req.query = simple_query;
183
+ req.sort_specs.emplace_back(couchbase::core::utils::json::generate("_id"));
184
+ req.highlight_fields = { "description" };
185
+ auto resp = test::utils::execute(integration.cluster, req);
186
+ REQUIRE_SUCCESS(resp.ctx.ec);
187
+ REQUIRE(resp.rows[0].fragments["description"][0] == "<mark>Belgian</mark>-Style Quadrupel Ale");
188
+ }
189
+
190
+ SECTION("highlight style")
191
+ {
192
+ {
193
+ couchbase::core::operations::search_request req{};
194
+ req.index_name = index_name;
195
+ req.query = simple_query;
196
+ req.sort_specs.emplace_back(couchbase::core::utils::json::generate("_id"));
197
+ req.highlight_fields = { "description" };
198
+ req.highlight_style = couchbase::core::search_highlight_style::html;
199
+ auto resp = test::utils::execute(integration.cluster, req);
200
+ REQUIRE_SUCCESS(resp.ctx.ec);
201
+ REQUIRE(resp.rows[0].fragments["description"][0] == "<mark>Belgian</mark>-Style Quadrupel Ale");
202
+ }
203
+
204
+ {
205
+ couchbase::core::operations::search_request req{};
206
+ req.index_name = index_name;
207
+ req.query = simple_query;
208
+ req.sort_specs.emplace_back(couchbase::core::utils::json::generate("_id"));
209
+ req.highlight_fields = { "description" };
210
+ req.highlight_style = couchbase::core::search_highlight_style::ansi;
211
+ auto resp = test::utils::execute(integration.cluster, req);
212
+ REQUIRE_SUCCESS(resp.ctx.ec);
213
+ // TODO: is there a better way to compare ansi strings?
214
+ std::string snippet = resp.rows[0].fragments["description"][0];
215
+ std::string open = "\x1b[43m";
216
+ std::string close = "\x1b[0m";
217
+ snippet.replace(snippet.find(open), open.size(), "<mark>");
218
+ snippet.replace(snippet.find(close), close.size(), "</mark>");
219
+ REQUIRE(snippet == "<mark>Belgian</mark>-Style Quadrupel Ale");
220
+ }
221
+ }
222
+
223
+ SECTION("fields")
224
+ {
225
+ couchbase::core::operations::search_request req{};
226
+ req.index_name = index_name;
227
+ req.query = simple_query;
228
+ req.sort_specs.emplace_back(couchbase::core::utils::json::generate("_id"));
229
+ req.fields.emplace_back("description");
230
+ auto resp = test::utils::execute(integration.cluster, req);
231
+ REQUIRE_SUCCESS(resp.ctx.ec);
232
+ auto fields = couchbase::core::utils::json::parse(resp.rows[0].fields).get_object();
233
+ REQUIRE(fields.at("description").get_string() == "Belgian-Style Quadrupel Ale");
234
+ }
235
+
236
+ SECTION("sort")
237
+ {
238
+ couchbase::core::operations::search_request req{};
239
+ req.index_name = index_name;
240
+ req.query = simple_query;
241
+ req.sort_specs.emplace_back(couchbase::core::utils::json::generate("_score"));
242
+ req.timeout = std::chrono::seconds(1);
243
+ auto resp = test::utils::execute(integration.cluster, req);
244
+ REQUIRE_SUCCESS(resp.ctx.ec);
245
+ REQUIRE(resp.rows[0].id == "bear_republic_brewery-red_rocket_ale");
246
+ }
247
+
248
+ SECTION("term facet")
249
+ {
250
+ couchbase::core::operations::search_request req{};
251
+ req.index_name = index_name;
252
+ req.query = simple_query;
253
+ req.facets.insert(std::make_pair("type", R"({"field": "type", "size": 1})"));
254
+ auto resp = test::utils::execute(integration.cluster, req);
255
+ REQUIRE_SUCCESS(resp.ctx.ec);
256
+ REQUIRE(resp.facets.size() == 1);
257
+ REQUIRE(resp.facets[0].name == "type");
258
+ REQUIRE(resp.facets[0].field == "type");
259
+ REQUIRE(resp.facets[0].total == 5);
260
+ REQUIRE(resp.facets[0].missing == 0);
261
+ REQUIRE(resp.facets[0].other == 0);
262
+ REQUIRE(resp.facets[0].terms.size() == 1);
263
+ REQUIRE(resp.facets[0].terms[0].term == "beer");
264
+ REQUIRE(resp.facets[0].terms[0].count == 5);
265
+ }
266
+
267
+ SECTION("date range facet")
268
+ {
269
+ couchbase::core::operations::search_request req{};
270
+ req.index_name = index_name;
271
+ req.query = simple_query;
272
+ req.facets.insert(std::make_pair(
273
+ "updated",
274
+ R"({"field": "updated", "size": 2, "date_ranges": [{"name": "old", "end": "2010-08-01"},{"name": "new", "start": "2010-08-01"}]})"));
275
+ auto resp = test::utils::execute(integration.cluster, req);
276
+ REQUIRE_SUCCESS(resp.ctx.ec);
277
+ REQUIRE(resp.facets.size() == 1);
278
+ REQUIRE(resp.facets[0].name == "updated");
279
+ REQUIRE(resp.facets[0].field == "updated");
280
+ REQUIRE(resp.facets[0].total == 5);
281
+ REQUIRE(resp.facets[0].missing == 0);
282
+ REQUIRE(resp.facets[0].other == 0);
283
+ REQUIRE(resp.facets[0].date_ranges.size() == 2);
284
+ REQUIRE(resp.facets[0].date_ranges[0].name == "old");
285
+ REQUIRE(resp.facets[0].date_ranges[0].count == 4);
286
+ REQUIRE_FALSE(resp.facets[0].date_ranges[0].start.has_value());
287
+ REQUIRE(resp.facets[0].date_ranges[0].end == "2010-08-01T00:00:00Z");
288
+ REQUIRE(resp.facets[0].date_ranges[1].name == "new");
289
+ REQUIRE(resp.facets[0].date_ranges[1].count == 1);
290
+ REQUIRE(resp.facets[0].date_ranges[1].start == "2010-08-01T00:00:00Z");
291
+ REQUIRE_FALSE(resp.facets[0].date_ranges[1].end.has_value());
292
+ }
293
+
294
+ SECTION("numeric range facet")
295
+ {
296
+ couchbase::core::operations::search_request req{};
297
+ req.index_name = index_name;
298
+ req.query = simple_query;
299
+ req.facets.insert(std::make_pair(
300
+ "abv", R"({"field": "abv", "size": 2, "numeric_ranges": [{"name": "high", "min": 7},{"name": "low", "max": 7}]})"));
301
+ auto resp = test::utils::execute(integration.cluster, req);
302
+ REQUIRE_SUCCESS(resp.ctx.ec);
303
+ REQUIRE(resp.facets.size() == 1);
304
+ REQUIRE(resp.facets[0].name == "abv");
305
+ REQUIRE(resp.facets[0].field == "abv");
306
+ REQUIRE(resp.facets[0].total == 5);
307
+ REQUIRE(resp.facets[0].missing == 0);
308
+ REQUIRE(resp.facets[0].other == 0);
309
+ REQUIRE(resp.facets[0].numeric_ranges.size() == 2);
310
+ auto high_range = std::find_if(resp.facets[0].numeric_ranges.begin(), resp.facets[0].numeric_ranges.end(), [](const auto& range) {
311
+ return range.name == "high";
312
+ });
313
+ REQUIRE(high_range != resp.facets[0].numeric_ranges.end());
314
+ REQUIRE(high_range->count == 2);
315
+ REQUIRE(std::get<std::uint64_t>(high_range->min) == 7);
316
+ REQUIRE(std::holds_alternative<std::monostate>(high_range->max));
317
+ auto low_range = std::find_if(resp.facets[0].numeric_ranges.begin(), resp.facets[0].numeric_ranges.end(), [](const auto& range) {
318
+ return range.name == "low";
319
+ });
320
+ REQUIRE(low_range != resp.facets[0].numeric_ranges.end());
321
+ REQUIRE(low_range->count == 3);
322
+ REQUIRE(std::holds_alternative<std::monostate>(low_range->min));
323
+ REQUIRE(std::get<std::uint64_t>(low_range->max) == 7);
324
+ }
325
+
326
+ SECTION("raw")
327
+ {
328
+ couchbase::core::operations::search_request req{};
329
+ req.index_name = index_name;
330
+ req.query = simple_query;
331
+ std::map<std::string, couchbase::core::json_string> raw{};
332
+ raw.insert(std::make_pair("size", couchbase::core::json_string("1")));
333
+ req.raw = raw;
334
+ auto resp = test::utils::execute(integration.cluster, req);
335
+ REQUIRE_SUCCESS(resp.ctx.ec);
336
+ REQUIRE(resp.rows.size() == 1);
337
+ }
338
+
339
+ {
340
+ couchbase::core::operations::management::search_index_drop_request req{};
341
+ req.index_name = index_name;
342
+ auto resp = test::utils::execute(integration.cluster, req);
343
+ REQUIRE_SUCCESS(resp.ctx.ec);
344
+ }
345
+ }
346
+
347
+ TEST_CASE("integration: search query consistency", "[integration]")
348
+ {
349
+ test::utils::integration_test_guard integration;
350
+
351
+ test::utils::open_bucket(integration.cluster, integration.ctx.bucket);
352
+
353
+ const std::string params =
354
+ R"(
355
+ {
356
+ "mapping": {
357
+ "default_mapping": {
358
+ "enabled": true,
359
+ "dynamic": true
360
+ },
361
+ "default_type": "_default",
362
+ "default_analyzer": "standard",
363
+ "default_field": "_all"
364
+ },
365
+ "doc_config": {
366
+ "mode": "type_field",
367
+ "type_field": "_type"
368
+ }
369
+ }
370
+ )";
371
+
372
+ auto index_name = test::utils::uniq_id("search_index");
373
+
374
+ {
375
+ couchbase::core::management::search::index index{};
376
+ index.name = index_name;
377
+ index.params_json = params;
378
+ index.type = "fulltext-index";
379
+ index.source_name = integration.ctx.bucket;
380
+ index.source_type = "couchbase";
381
+ if (integration.cluster_version().requires_search_replicas()) {
382
+ index.plan_params_json = couchbase::core::utils::json::generate({
383
+ { "indexPartitions", 1 },
384
+ { "numReplicas", 1 },
385
+ });
386
+ }
387
+ couchbase::core::operations::management::search_index_upsert_request req{};
388
+ req.index = index;
389
+ auto resp = test::utils::execute(integration.cluster, req);
390
+ REQUIRE_SUCCESS(resp.ctx.ec);
391
+ if (index_name != resp.name) {
392
+ CB_LOG_INFO("update index name \"{}\" -> \"{}\"", index_name, resp.name);
393
+ }
394
+ index_name = resp.name;
395
+ }
396
+
397
+ REQUIRE(test::utils::wait_for_search_pindexes_ready(integration.cluster, integration.ctx.bucket, index_name));
398
+
399
+ auto value = test::utils::uniq_id("value");
400
+ auto id = couchbase::core::document_id(integration.ctx.bucket, "_default", "_default", test::utils::uniq_id("key"));
401
+
402
+ // FIXME: MB-55920, consistency checks is broken in all known versions of the servers at the moment,
403
+ // it might return empty results without waiting for the mutation. We cannot workaround it in any way.
404
+ // We know that doing a mutation, and then query with consistency checks in the loop is not proper test,
405
+ // but at least it will check the payload format for now, and later when the issue is fixed, the loop
406
+ // has to be removed.
407
+ couchbase::mutation_token token;
408
+ {
409
+ // now update the document and use its mutation token in query later
410
+ auto resp = test::utils::execute(integration.cluster,
411
+ couchbase::core::operations::upsert_request{
412
+ id,
413
+ couchbase::core::utils::json::generate_binary(tao::json::value{
414
+ { "_type", "test_doc" },
415
+ { "value", value },
416
+ }),
417
+ });
418
+ REQUIRE_SUCCESS(resp.ctx.ec());
419
+ token = resp.token;
420
+ }
421
+
422
+ /*
423
+ * Retry query with consistency check until it will succeed or reach 20 attempts.
424
+ *
425
+ * FTS might return empty results. See MB-55920.
426
+ */
427
+ int attempt = 0;
428
+ bool done = false;
429
+ while (!done) {
430
+ const tao::json::value query{ { "query", fmt::format("value:{}", value) } };
431
+ auto query_json = couchbase::core::json_string(couchbase::core::utils::json::generate(query));
432
+
433
+ {
434
+ couchbase::core::operations::search_request req{};
435
+ req.index_name = index_name;
436
+ req.query = query_json;
437
+ req.mutation_state.emplace_back(token);
438
+ auto resp = test::utils::execute(integration.cluster, req);
439
+ if (resp.ctx.ec == couchbase::errc::search::consistency_mismatch) {
440
+ // FIXME(MB-55920): ignore "err: bleve: pindex_consistency mismatched partition"
441
+ CB_LOG_INFO("ignore consistency_mismatch: {}", resp.ctx.http_body);
442
+ continue;
443
+ }
444
+ INFO(resp.ctx.http_body)
445
+ REQUIRE_SUCCESS(resp.ctx.ec);
446
+ switch (resp.rows.size()) {
447
+ case 1:
448
+ done = true;
449
+ break;
450
+
451
+ case 0:
452
+ if (attempt > 20) {
453
+ FAIL("Unable to use search query with consistency. Giving up.");
454
+ }
455
+ ++attempt;
456
+ break;
457
+
458
+ default:
459
+ REQUIRE(resp.rows.size() == 1);
460
+ break;
461
+ }
462
+ }
463
+ }
464
+
465
+ {
466
+ couchbase::core::operations::management::search_index_drop_request req{};
467
+ req.index_name = index_name;
468
+ auto resp = test::utils::execute(integration.cluster, req);
469
+ REQUIRE_SUCCESS(resp.ctx.ec);
470
+ }
471
+ }
472
+
473
+ TEST_CASE("integration: search query collections")
474
+ {
475
+ test::utils::integration_test_guard integration;
476
+
477
+ if (!integration.cluster_version().supports_collections()) {
478
+ return;
479
+ }
480
+
481
+ test::utils::open_bucket(integration.cluster, integration.ctx.bucket);
482
+
483
+ auto index_name = test::utils::uniq_id("search_index");
484
+ auto collection1_name = test::utils::uniq_id("collection");
485
+ auto collection2_name = test::utils::uniq_id("collection");
486
+ std::string doc = R"({"name": "test"})";
487
+
488
+ for (const auto& collection : { collection1_name, collection2_name }) {
489
+ {
490
+ couchbase::core::operations::management::collection_create_request req{ integration.ctx.bucket, "_default", collection };
491
+ auto resp = test::utils::execute(integration.cluster, req);
492
+ REQUIRE_SUCCESS(resp.ctx.ec);
493
+ auto created = test::utils::wait_until_collection_manifest_propagated(integration.cluster, integration.ctx.bucket, resp.uid);
494
+ REQUIRE(created);
495
+ }
496
+
497
+ {
498
+ auto key = test::utils::uniq_id("key");
499
+ auto id = couchbase::core::document_id(integration.ctx.bucket, "_default", collection, key);
500
+ couchbase::core::operations::upsert_request req{ id, couchbase::core::utils::to_binary(doc) };
501
+ auto resp = test::utils::execute(integration.cluster, req);
502
+ REQUIRE_SUCCESS(resp.ctx.ec());
503
+ }
504
+ }
505
+
506
+ {
507
+ // clang-format off
508
+ std::string params =
509
+ R"(
510
+ {
511
+ "mapping": {
512
+ "types": {
513
+ "_default.)" + collection1_name + R"(": {
514
+ "enabled": true,
515
+ "dynamic": true
516
+ },
517
+ "_default.)" + collection2_name + R"(": {
518
+ "enabled": true,
519
+ "dynamic": true
520
+ }
521
+ },
522
+ "default_mapping": {
523
+ "enabled": false
524
+ },
525
+ "default_type": "_default",
526
+ "default_analyzer": "standard",
527
+ "default_field": "_all"
528
+ },
529
+ "doc_config": {
530
+ "mode": "scope.collection.type_field"
531
+ }
532
+ }
533
+ )";
534
+ // clang-format on
535
+
536
+ couchbase::core::management::search::index index{};
537
+ index.name = index_name;
538
+ index.params_json = params;
539
+ index.type = "fulltext-index";
540
+ index.source_name = integration.ctx.bucket;
541
+ index.source_type = "couchbase";
542
+ if (integration.cluster_version().requires_search_replicas()) {
543
+ index.plan_params_json = couchbase::core::utils::json::generate({
544
+ { "indexPartitions", 1 },
545
+ { "numReplicas", 1 },
546
+ });
547
+ }
548
+ couchbase::core::operations::management::search_index_upsert_request req{};
549
+ req.index = index;
550
+ auto resp = test::utils::execute(integration.cluster, req);
551
+ REQUIRE_SUCCESS(resp.ctx.ec);
552
+ if (index_name != resp.name) {
553
+ CB_LOG_INFO("update index name \"{}\" -> \"{}\"", index_name, resp.name);
554
+ }
555
+ index_name = resp.name;
556
+ }
557
+
558
+ REQUIRE(test::utils::wait_until_indexed(integration.cluster, index_name, 2));
559
+
560
+ couchbase::core::json_string simple_query(R"({"query": "name:test"})");
561
+
562
+ // no collection parameter - both docs returned
563
+ {
564
+ couchbase::core::operations::search_request req{};
565
+ req.index_name = index_name;
566
+ req.query = simple_query;
567
+ auto resp = test::utils::execute(integration.cluster, req);
568
+ REQUIRE_SUCCESS(resp.ctx.ec);
569
+ REQUIRE(resp.rows.size() == 2);
570
+ }
571
+
572
+ // one collection - only docs from that collection returned
573
+ {
574
+ couchbase::core::operations::search_request req{};
575
+ req.index_name = index_name;
576
+ req.query = simple_query;
577
+ req.collections.emplace_back(collection1_name);
578
+ auto resp = test::utils::execute(integration.cluster, req);
579
+ REQUIRE_SUCCESS(resp.ctx.ec);
580
+ REQUIRE(resp.rows.size() == 1);
581
+ }
582
+
583
+ // two collections - both docs returned
584
+ {
585
+ couchbase::core::operations::search_request req{};
586
+ req.index_name = index_name;
587
+ req.query = simple_query;
588
+ req.collections.emplace_back(collection1_name);
589
+ req.collections.emplace_back(collection2_name);
590
+ auto resp = test::utils::execute(integration.cluster, req);
591
+ REQUIRE_SUCCESS(resp.ctx.ec);
592
+ REQUIRE(resp.rows.size() == 2);
593
+ }
594
+
595
+ {
596
+ couchbase::core::operations::management::search_index_drop_request req{};
597
+ req.index_name = index_name;
598
+ auto resp = test::utils::execute(integration.cluster, req);
599
+ REQUIRE_SUCCESS(resp.ctx.ec);
600
+ }
601
+ }
@@ -896,4 +896,77 @@ TEST_CASE("transactions: atr and client_record are binary documents", "[transact
896
896
  REQUIRE_SUCCESS(resp.ctx.ec());
897
897
  REQUIRE(resp.value == binary_null);
898
898
  }
899
+ }
900
+ TEST_CASE("transactions: get non-existent doc fails txn", "[transactions]")
901
+ {
902
+ test::utils::integration_test_guard integration;
903
+ auto cluster = integration.cluster;
904
+ transactions txn(cluster, get_conf());
905
+ couchbase::core::document_id id{ integration.ctx.bucket, "_default", "_default", test::utils::uniq_id("txn") };
906
+ REQUIRE_THROWS_AS(txn.run([id](attempt_context& ctx) { ctx.get(id); }), transaction_exception);
907
+ }
908
+
909
+ TEST_CASE("transactions: get_optional on non-existent doc doesn't fail txn", "[transactions]")
910
+ {
911
+ test::utils::integration_test_guard integration;
912
+ auto cluster = integration.cluster;
913
+ transactions txn(cluster, get_conf());
914
+ couchbase::core::document_id id{ integration.ctx.bucket, "_default", "_default", test::utils::uniq_id("txn") };
915
+ REQUIRE_NOTHROW(txn.run([id](attempt_context& ctx) { ctx.get_optional(id); }));
916
+ }
917
+ TEST_CASE("transactions: get after query behaves same as before a query", "[transactions]")
918
+ {
919
+ test::utils::integration_test_guard integration;
920
+ auto cluster = integration.cluster;
921
+ transactions txn(cluster, get_conf());
922
+ couchbase::core::document_id id{ integration.ctx.bucket, "_default", "_default", test::utils::uniq_id("txn") };
923
+ REQUIRE_THROWS_AS(txn.run([id](attempt_context& ctx) {
924
+ ctx.query("select * from `default` limit 1");
925
+ ctx.get(id);
926
+ }),
927
+ transaction_exception);
928
+ }
929
+
930
+ TEST_CASE("transactions: get_optional after query behaves same as before a query", "[transactions]")
931
+ {
932
+ test::utils::integration_test_guard integration;
933
+ auto cluster = integration.cluster;
934
+ transactions txn(cluster, get_conf());
935
+ couchbase::core::document_id id{ integration.ctx.bucket, "_default", "_default", test::utils::uniq_id("txn") };
936
+ REQUIRE_NOTHROW(txn.run([id](attempt_context& ctx) {
937
+ ctx.query("select * from `default` limit 1");
938
+ ctx.get_optional(id);
939
+ }));
940
+ }
941
+ TEST_CASE("transactions: sergey example", "[transactions]")
942
+ {
943
+ test::utils::integration_test_guard integration;
944
+ auto cluster = integration.cluster;
945
+ transactions txn(cluster, get_conf());
946
+ couchbase::core::document_id id_to_remove{ integration.ctx.bucket, "_default", "_default", test::utils::uniq_id("txn") };
947
+ couchbase::core::document_id id_to_replace{ integration.ctx.bucket, "_default", "_default", test::utils::uniq_id("txn") };
948
+ couchbase::core::document_id id_to_insert{ integration.ctx.bucket, "_default", "_default", test::utils::uniq_id("txn") };
949
+ {
950
+ couchbase::core::operations::upsert_request req{ id_to_remove, content_json };
951
+ auto resp = test::utils::execute(integration.cluster, req);
952
+ REQUIRE_SUCCESS(resp.ctx.ec());
953
+ }
954
+ {
955
+ couchbase::core::operations::upsert_request req{ id_to_replace, content_json };
956
+ auto resp = test::utils::execute(integration.cluster, req);
957
+ REQUIRE_SUCCESS(resp.ctx.ec());
958
+ }
959
+
960
+ REQUIRE_NOTHROW(txn.run([&](attempt_context& ctx) {
961
+ ctx.query(fmt::format(
962
+ "INSERT INTO `default` (KEY, VALUE) VALUES ('{}', {})", id_to_insert.key(), couchbase::core::utils::json::generate(content)));
963
+ ctx.query(fmt::format("UPDATE `default` USE KEYS '{}' SET `some_number` = 10 ", id_to_replace.key()));
964
+ ctx.query(fmt::format("DELETE FROM `default` WHERE META().id = '{}'", id_to_remove.key()));
965
+ auto insert_res = ctx.get(id_to_insert);
966
+ CHECK(insert_res.content<tao::json::value>() == content);
967
+ auto replace_res = ctx.get(id_to_replace);
968
+ CHECK(replace_res.content<tao::json::value>()["some_number"] == 10);
969
+ auto remove_res = ctx.get_optional(id_to_remove);
970
+ CHECK_FALSE(remove_res.has_value());
971
+ }));
899
972
  }