couchbase 4.2.6 → 4.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. package/deps/couchbase-cxx-client/.github/workflows/windows.yml +0 -3
  2. package/deps/couchbase-cxx-client/CMakeLists.txt +4 -0
  3. package/deps/couchbase-cxx-client/bin/build-tests.rb +1 -1
  4. package/deps/couchbase-cxx-client/core/impl/lookup_in.cxx +1 -0
  5. package/deps/couchbase-cxx-client/core/impl/lookup_in_all_replicas.cxx +176 -0
  6. package/deps/couchbase-cxx-client/core/impl/lookup_in_all_replicas.hxx +80 -0
  7. package/deps/couchbase-cxx-client/core/impl/lookup_in_any_replica.cxx +167 -0
  8. package/deps/couchbase-cxx-client/core/impl/lookup_in_any_replica.hxx +75 -0
  9. package/deps/couchbase-cxx-client/core/impl/lookup_in_replica.cxx +97 -0
  10. package/deps/couchbase-cxx-client/core/impl/lookup_in_replica.hxx +67 -0
  11. package/deps/couchbase-cxx-client/core/meta/features.hxx +13 -0
  12. package/deps/couchbase-cxx-client/core/operations/document_lookup_in_all_replicas.hxx +192 -0
  13. package/deps/couchbase-cxx-client/core/operations/document_lookup_in_any_replica.hxx +188 -0
  14. package/deps/couchbase-cxx-client/core/operations.hxx +2 -0
  15. package/deps/couchbase-cxx-client/core/protocol/cmd_hello.hxx +1 -0
  16. package/deps/couchbase-cxx-client/core/protocol/cmd_lookup_in_replica.cxx +107 -0
  17. package/deps/couchbase-cxx-client/core/protocol/cmd_lookup_in_replica.hxx +137 -0
  18. package/deps/couchbase-cxx-client/core/protocol/hello_feature.hxx +6 -0
  19. package/deps/couchbase-cxx-client/core/protocol/hello_feature_fmt.hxx +3 -0
  20. package/deps/couchbase-cxx-client/core/topology/capabilities.hxx +1 -0
  21. package/deps/couchbase-cxx-client/core/topology/capabilities_fmt.hxx +3 -0
  22. package/deps/couchbase-cxx-client/core/topology/configuration.hxx +5 -0
  23. package/deps/couchbase-cxx-client/core/topology/configuration_json.hxx +2 -1
  24. package/deps/couchbase-cxx-client/couchbase/collection.hxx +111 -0
  25. package/deps/couchbase-cxx-client/couchbase/get_and_lock_options.hxx +2 -2
  26. package/deps/couchbase-cxx-client/couchbase/get_and_touch_options.hxx +2 -2
  27. package/deps/couchbase-cxx-client/couchbase/get_options.hxx +2 -2
  28. package/deps/couchbase-cxx-client/couchbase/insert_options.hxx +3 -3
  29. package/deps/couchbase-cxx-client/couchbase/lookup_in_all_replicas_options.hxx +109 -0
  30. package/deps/couchbase-cxx-client/couchbase/lookup_in_any_replica_options.hxx +101 -0
  31. package/deps/couchbase-cxx-client/couchbase/lookup_in_options.hxx +2 -2
  32. package/deps/couchbase-cxx-client/couchbase/lookup_in_replica_result.hxx +74 -0
  33. package/deps/couchbase-cxx-client/couchbase/lookup_in_result.hxx +26 -0
  34. package/deps/couchbase-cxx-client/couchbase/mutate_in_options.hxx +2 -2
  35. package/deps/couchbase-cxx-client/couchbase/remove_options.hxx +2 -2
  36. package/deps/couchbase-cxx-client/couchbase/replace_options.hxx +3 -3
  37. package/deps/couchbase-cxx-client/couchbase/touch_options.hxx +2 -2
  38. package/deps/couchbase-cxx-client/couchbase/unlock_options.hxx +2 -2
  39. package/deps/couchbase-cxx-client/couchbase/upsert_options.hxx +3 -3
  40. package/deps/couchbase-cxx-client/test/test_integration_subdoc.cxx +655 -0
  41. package/dist/binding.d.ts +45 -0
  42. package/dist/collection.d.ts +53 -1
  43. package/dist/collection.js +139 -1
  44. package/dist/crudoptypes.d.ts +24 -0
  45. package/dist/crudoptypes.js +14 -1
  46. package/package.json +13 -13
  47. package/src/connection.cpp +4 -0
  48. package/src/connection.hpp +2 -0
  49. package/src/connection_autogen.cpp +28 -0
  50. package/src/jstocbpp_autogen.hpp +262 -0
@@ -65,6 +65,108 @@ assert_single_lookup_error(test::utils::integration_test_guard& integration,
65
65
  REQUIRE(resp.fields[0].ec == expected_ec);
66
66
  }
67
67
 
68
+ template<typename SubdocumentOperation>
69
+ void
70
+ assert_single_lookup_any_replica_success(test::utils::integration_test_guard& integration,
71
+ const couchbase::core::document_id& id,
72
+ const SubdocumentOperation& spec,
73
+ std::optional<std::string> expected_value = std::nullopt)
74
+ {
75
+ couchbase::core::operations::lookup_in_any_replica_request req{ id };
76
+ req.specs = couchbase::lookup_in_specs{ spec }.specs();
77
+ auto resp = test::utils::execute(integration.cluster, req);
78
+ INFO(fmt::format("assert_single_lookup_all_replica_success(\"{}\", \"{}\")", id, req.specs[0].path_));
79
+ REQUIRE_SUCCESS(resp.ctx.ec());
80
+ REQUIRE_FALSE(resp.cas.empty());
81
+ REQUIRE(resp.fields.size() == 1);
82
+ REQUIRE(resp.fields[0].exists);
83
+ REQUIRE(resp.fields[0].path == req.specs[0].path_);
84
+ REQUIRE(resp.fields[0].status == couchbase::key_value_status_code::success);
85
+ REQUIRE_SUCCESS(resp.fields[0].ec);
86
+ if (expected_value.has_value()) {
87
+ REQUIRE(couchbase::core::utils::to_binary(expected_value.value()) == resp.fields[0].value);
88
+ }
89
+ }
90
+
91
+ template<typename SubdocumentOperation>
92
+ void
93
+ assert_single_lookup_any_replica_error(test::utils::integration_test_guard& integration,
94
+ const couchbase::core::document_id& id,
95
+ const SubdocumentOperation& spec,
96
+ couchbase::key_value_status_code expected_status,
97
+ std::error_code expected_ec)
98
+ {
99
+ couchbase::core::operations::lookup_in_any_replica_request req{ id };
100
+ req.specs = couchbase::lookup_in_specs{ spec }.specs();
101
+ auto resp = test::utils::execute(integration.cluster, req);
102
+ INFO(fmt::format("assert_single_lookup_all_replica_error(\"{}\", \"{}\")", id, req.specs[0].path_));
103
+ REQUIRE_SUCCESS(resp.ctx.ec());
104
+ REQUIRE_FALSE(resp.cas.empty());
105
+ REQUIRE(resp.fields.size() == 1);
106
+ REQUIRE_FALSE(resp.fields[0].exists);
107
+ REQUIRE(resp.fields[0].path == req.specs[0].path_);
108
+ REQUIRE(resp.fields[0].value.empty());
109
+ REQUIRE(resp.fields[0].status == expected_status);
110
+ REQUIRE(resp.fields[0].ec == expected_ec);
111
+ }
112
+
113
+ template<typename SubdocumentOperation>
114
+ void
115
+ assert_single_lookup_all_replica_success(test::utils::integration_test_guard& integration,
116
+ const couchbase::core::document_id& id,
117
+ const SubdocumentOperation& spec,
118
+ std::optional<std::string> expected_value = std::nullopt)
119
+ {
120
+ couchbase::core::operations::lookup_in_all_replicas_request req{ id };
121
+ req.specs = couchbase::lookup_in_specs{ spec }.specs();
122
+ auto response = test::utils::execute(integration.cluster, req);
123
+ INFO(fmt::format("assert_single_lookup_all_replica_success(\"{}\", \"{}\")", id, req.specs[0].path_));
124
+ REQUIRE_SUCCESS(response.ctx.ec());
125
+ REQUIRE(response.entries.size() == integration.number_of_replicas() + 1);
126
+ auto responses_from_active =
127
+ std::count_if(response.entries.begin(), response.entries.end(), [](const auto& r) { return !r.is_replica; });
128
+ REQUIRE(responses_from_active == 1);
129
+ for (auto& resp : response.entries) {
130
+ REQUIRE_FALSE(resp.cas.empty());
131
+ REQUIRE(resp.fields.size() == 1);
132
+ REQUIRE(resp.fields[0].exists);
133
+ REQUIRE(resp.fields[0].path == req.specs[0].path_);
134
+ REQUIRE(resp.fields[0].status == couchbase::key_value_status_code::success);
135
+ REQUIRE_SUCCESS(resp.fields[0].ec);
136
+ if (expected_value.has_value()) {
137
+ REQUIRE(couchbase::core::utils::to_binary(expected_value.value()) == resp.fields[0].value);
138
+ }
139
+ }
140
+ }
141
+
142
+ template<typename SubdocumentOperation>
143
+ void
144
+ assert_single_lookup_all_replica_error(test::utils::integration_test_guard& integration,
145
+ const couchbase::core::document_id& id,
146
+ const SubdocumentOperation& spec,
147
+ couchbase::key_value_status_code expected_status,
148
+ std::error_code expected_ec)
149
+ {
150
+ couchbase::core::operations::lookup_in_all_replicas_request req{ id };
151
+ req.specs = couchbase::lookup_in_specs{ spec }.specs();
152
+ auto response = test::utils::execute(integration.cluster, req);
153
+ INFO(fmt::format("assert_single_lookup_all_replica_error(\"{}\", \"{}\")", id, req.specs[0].path_));
154
+ REQUIRE_SUCCESS(response.ctx.ec());
155
+ REQUIRE(response.entries.size() == integration.number_of_replicas() + 1);
156
+ auto responses_from_active =
157
+ std::count_if(response.entries.begin(), response.entries.end(), [](const auto& r) { return !r.is_replica; });
158
+ REQUIRE(responses_from_active == 1);
159
+ for (auto& resp : response.entries) {
160
+ REQUIRE_FALSE(resp.cas.empty());
161
+ REQUIRE(resp.fields.size() == 1);
162
+ REQUIRE_FALSE(resp.fields[0].exists);
163
+ REQUIRE(resp.fields[0].path == req.specs[0].path_);
164
+ REQUIRE(resp.fields[0].value.empty());
165
+ REQUIRE(resp.fields[0].status == expected_status);
166
+ REQUIRE(resp.fields[0].ec == expected_ec);
167
+ }
168
+ }
169
+
68
170
  void
69
171
  assert_single_mutate_success(couchbase::core::operations::mutate_in_response resp, const std::string& path, const std::string& value = "")
70
172
  {
@@ -973,3 +1075,556 @@ TEST_CASE("integration: subdoc top level array", "[integration]")
973
1075
  REQUIRE(resp.fields[0].value == couchbase::core::utils::to_binary("3"));
974
1076
  }
975
1077
  }
1078
+
1079
+ TEST_CASE("integration: subdoc all replica reads", "[integration]")
1080
+ {
1081
+
1082
+ test::utils::integration_test_guard integration;
1083
+
1084
+ if (!integration.has_bucket_capability("subdoc.ReplicaRead")) {
1085
+ SKIP("cluster does not support replica_read");
1086
+ }
1087
+
1088
+ auto number_of_replicas = integration.number_of_replicas();
1089
+
1090
+ if (number_of_replicas == 0) {
1091
+ SKIP("bucket has zero replicas");
1092
+ }
1093
+ if (integration.number_of_nodes() <= number_of_replicas) {
1094
+ SKIP(fmt::format("number of nodes ({}) is less or equal to number of replicas ({})",
1095
+ integration.number_of_nodes(),
1096
+ integration.number_of_replicas()));
1097
+ }
1098
+
1099
+ auto key = test::utils::uniq_id("lookup_in_any_replica");
1100
+ couchbase::core::document_id id{ integration.ctx.bucket, "_default", "_default", key };
1101
+
1102
+ {
1103
+ auto value_json = couchbase::core::utils::to_binary(R"({"dictkey":"dictval","array":[1,2,3,4,[10,20,30,[100,200,300]]]})");
1104
+ couchbase::core::operations::insert_request req{ id, value_json };
1105
+ req.durability_level = couchbase::durability_level::majority_and_persist_to_active;
1106
+ auto resp = test::utils::execute(integration.cluster, req);
1107
+ REQUIRE_SUCCESS(resp.ctx.ec());
1108
+ }
1109
+
1110
+ SECTION("dict get")
1111
+ {
1112
+ assert_single_lookup_all_replica_success(integration, id, couchbase::lookup_in_specs::get("dictkey"), R"("dictval")");
1113
+ }
1114
+
1115
+ SECTION("dict exists")
1116
+ {
1117
+ assert_single_lookup_all_replica_success(integration, id, couchbase::lookup_in_specs::exists("dictkey"));
1118
+ }
1119
+
1120
+ SECTION("array get")
1121
+ {
1122
+ assert_single_lookup_all_replica_success(
1123
+ integration, id, couchbase::lookup_in_specs::get("array"), "[1,2,3,4,[10,20,30,[100,200,300]]]");
1124
+ }
1125
+
1126
+ SECTION("array exists")
1127
+ {
1128
+ assert_single_lookup_all_replica_success(integration, id, couchbase::lookup_in_specs::exists("array"));
1129
+ }
1130
+
1131
+ SECTION("array index get")
1132
+ {
1133
+ assert_single_lookup_all_replica_success(integration, id, couchbase::lookup_in_specs::get("array[0]"), "1");
1134
+ }
1135
+
1136
+ SECTION("array index exists")
1137
+ {
1138
+ assert_single_lookup_all_replica_success(integration, id, couchbase::lookup_in_specs::exists("array[0]"));
1139
+ }
1140
+
1141
+ SECTION("non existent path get")
1142
+ {
1143
+ assert_single_lookup_all_replica_error(integration,
1144
+ id,
1145
+ couchbase::lookup_in_specs::get("non-exist"),
1146
+ couchbase::key_value_status_code::subdoc_path_not_found,
1147
+ couchbase::errc::key_value::path_not_found);
1148
+ }
1149
+
1150
+ SECTION("non existent path exists")
1151
+ {
1152
+ assert_single_lookup_all_replica_error(integration,
1153
+ id,
1154
+ couchbase::lookup_in_specs::exists("non-exist"),
1155
+ couchbase::key_value_status_code::subdoc_path_not_found,
1156
+ couchbase::errc::key_value::path_not_found);
1157
+ }
1158
+
1159
+ SECTION("non existent doc")
1160
+ {
1161
+ couchbase::core::document_id missing_id{ integration.ctx.bucket, "_default", "_default", "missing_key" };
1162
+
1163
+ SECTION("non existent doc get")
1164
+ {
1165
+ couchbase::core::operations::lookup_in_all_replicas_request req{ missing_id };
1166
+ req.specs =
1167
+ couchbase::lookup_in_specs{
1168
+ couchbase::lookup_in_specs::get("non-exist"),
1169
+ }
1170
+ .specs();
1171
+ auto resp = test::utils::execute(integration.cluster, req);
1172
+ REQUIRE(resp.ctx.ec() == couchbase::errc::key_value::document_not_found);
1173
+ REQUIRE(resp.entries.empty());
1174
+ }
1175
+
1176
+ SECTION("non existent doc exists")
1177
+ {
1178
+ couchbase::core::operations::lookup_in_all_replicas_request req{ missing_id };
1179
+ req.specs =
1180
+ couchbase::lookup_in_specs{
1181
+ couchbase::lookup_in_specs::exists("non-exist"),
1182
+ }
1183
+ .specs();
1184
+ auto resp = test::utils::execute(integration.cluster, req);
1185
+ REQUIRE(resp.ctx.ec() == couchbase::errc::key_value::document_not_found);
1186
+ REQUIRE(resp.entries.empty());
1187
+ }
1188
+ }
1189
+
1190
+ SECTION("non json")
1191
+ {
1192
+ couchbase::core::document_id non_json_id{ integration.ctx.bucket, "_default", "_default", test::utils::uniq_id("non_json") };
1193
+ auto non_json_doc = couchbase::core::utils::to_binary("string");
1194
+
1195
+ {
1196
+ couchbase::core::operations::insert_request req{ non_json_id, non_json_doc };
1197
+ auto resp = test::utils::execute(integration.cluster, req);
1198
+ REQUIRE_SUCCESS(resp.ctx.ec());
1199
+ }
1200
+
1201
+ SECTION("non json get")
1202
+ {
1203
+ if (integration.cluster_version().is_mock()) {
1204
+ SKIP("GOCAVES does not handle subdocument operations for non-JSON documents. See "
1205
+ "https://github.com/couchbaselabs/gocaves/issues/103");
1206
+ }
1207
+ assert_single_lookup_all_replica_error(integration,
1208
+ non_json_id,
1209
+ couchbase::lookup_in_specs::get("non-exist"),
1210
+ couchbase::key_value_status_code::subdoc_doc_not_json,
1211
+ couchbase::errc::key_value::document_not_json);
1212
+ }
1213
+
1214
+ SECTION("non json exists")
1215
+ {
1216
+ if (integration.cluster_version().is_mock()) {
1217
+ SKIP("GOCAVES does not handle subdocument operations for non-JSON documents. See "
1218
+ "https://github.com/couchbaselabs/gocaves/issues/103");
1219
+ }
1220
+ assert_single_lookup_all_replica_error(integration,
1221
+ non_json_id,
1222
+ couchbase::lookup_in_specs::exists("non-exist"),
1223
+ couchbase::key_value_status_code::subdoc_doc_not_json,
1224
+ couchbase::errc::key_value::document_not_json);
1225
+ }
1226
+ }
1227
+
1228
+ SECTION("invalid path")
1229
+ {
1230
+ std::vector<std::string> invalid_paths = { "invalid..path", "invalid[-2]" };
1231
+ for (const auto& path : invalid_paths) {
1232
+ if (integration.cluster_version().is_mock()) {
1233
+ assert_single_lookup_all_replica_error(integration,
1234
+ id,
1235
+ couchbase::lookup_in_specs::get(path),
1236
+ couchbase::key_value_status_code::subdoc_path_not_found,
1237
+ couchbase::errc::key_value::path_not_found);
1238
+ } else {
1239
+ assert_single_lookup_all_replica_error(integration,
1240
+ id,
1241
+ couchbase::lookup_in_specs::get(path),
1242
+ couchbase::key_value_status_code::subdoc_path_invalid,
1243
+ couchbase::errc::key_value::path_invalid);
1244
+ }
1245
+ }
1246
+ }
1247
+
1248
+ SECTION("negative paths")
1249
+ {
1250
+ assert_single_lookup_all_replica_success(integration, id, couchbase::lookup_in_specs::get("array[-1][-1][-1]"), "300");
1251
+ }
1252
+
1253
+ SECTION("nested arrays")
1254
+ {
1255
+ assert_single_lookup_all_replica_success(integration, id, couchbase::lookup_in_specs::get("array[4][3][2]"), "300");
1256
+ }
1257
+
1258
+ SECTION("path mismatch")
1259
+ {
1260
+ assert_single_lookup_all_replica_error(integration,
1261
+ id,
1262
+ couchbase::lookup_in_specs::get("array.key"),
1263
+ couchbase::key_value_status_code::subdoc_path_mismatch,
1264
+ couchbase::errc::key_value::path_mismatch);
1265
+ }
1266
+
1267
+ SECTION("public API")
1268
+ {
1269
+ auto collection = couchbase::cluster(integration.cluster).bucket(integration.ctx.bucket).scope("_default").collection("_default");
1270
+
1271
+ SECTION("lookup in all replicas")
1272
+ {
1273
+ auto specs = couchbase::lookup_in_specs{ couchbase::lookup_in_specs::get("dictkey"),
1274
+ couchbase::lookup_in_specs::exists("array"),
1275
+ couchbase::lookup_in_specs::count("array") };
1276
+ auto [ctx, result] = collection.lookup_in_all_replicas(key, specs).get();
1277
+ REQUIRE_SUCCESS(ctx.ec());
1278
+ REQUIRE(result.size() == number_of_replicas + 1);
1279
+ auto responses_from_active = std::count_if(result.begin(), result.end(), [](const auto& r) { return !r.is_replica(); });
1280
+ REQUIRE(responses_from_active == 1);
1281
+ for (auto& res : result) {
1282
+ REQUIRE(!res.cas().empty());
1283
+ REQUIRE("dictval" == res.content_as<std::string>(0));
1284
+ REQUIRE(res.exists("array"));
1285
+ REQUIRE(5 == res.content_as<int>(2));
1286
+ }
1287
+ }
1288
+
1289
+ SECTION("missing document")
1290
+ {
1291
+ auto specs = couchbase::lookup_in_specs{
1292
+ couchbase::lookup_in_specs::get("non-exists"),
1293
+ };
1294
+ auto [ctx, result] = collection.lookup_in_all_replicas("missing-key", specs).get();
1295
+ REQUIRE(ctx.ec() == couchbase::errc::key_value::document_not_found);
1296
+ REQUIRE(result.empty());
1297
+ }
1298
+ }
1299
+ }
1300
+
1301
+ TEST_CASE("integration: subdoc any replica reads", "[integration]")
1302
+ {
1303
+ test::utils::integration_test_guard integration;
1304
+
1305
+ if (!integration.has_bucket_capability("subdoc.ReplicaRead")) {
1306
+ SKIP("cluster does not support replica_read");
1307
+ }
1308
+
1309
+ auto number_of_replicas = integration.number_of_replicas();
1310
+
1311
+ if (number_of_replicas == 0) {
1312
+ SKIP("bucket has zero replicas");
1313
+ }
1314
+ if (integration.number_of_nodes() <= number_of_replicas) {
1315
+ SKIP(fmt::format("number of nodes ({}) is less or equal to number of replicas ({})",
1316
+ integration.number_of_nodes(),
1317
+ integration.number_of_replicas()));
1318
+ }
1319
+
1320
+ test::utils::open_bucket(integration.cluster, integration.ctx.bucket);
1321
+
1322
+ auto key = test::utils::uniq_id("lookup_in_any_replica");
1323
+ couchbase::core::document_id id{ integration.ctx.bucket, "_default", "_default", key };
1324
+
1325
+ {
1326
+ auto value_json = couchbase::core::utils::to_binary(R"({"dictkey":"dictval","array":[1,2,3,4,[10,20,30,[100,200,300]]]})");
1327
+ couchbase::core::operations::insert_request req{ id, value_json };
1328
+ req.durability_level = couchbase::durability_level::majority_and_persist_to_active;
1329
+ auto resp = test::utils::execute(integration.cluster, req);
1330
+ REQUIRE_SUCCESS(resp.ctx.ec());
1331
+ }
1332
+
1333
+ SECTION("dict get")
1334
+ {
1335
+ assert_single_lookup_any_replica_success(integration, id, couchbase::lookup_in_specs::get("dictkey"), R"("dictval")");
1336
+ }
1337
+
1338
+ SECTION("dict exists")
1339
+ {
1340
+ assert_single_lookup_any_replica_success(integration, id, couchbase::lookup_in_specs::exists("dictkey"));
1341
+ }
1342
+
1343
+ SECTION("array get")
1344
+ {
1345
+ assert_single_lookup_any_replica_success(
1346
+ integration, id, couchbase::lookup_in_specs::get("array"), "[1,2,3,4,[10,20,30,[100,200,300]]]");
1347
+ }
1348
+
1349
+ SECTION("array exists")
1350
+ {
1351
+ assert_single_lookup_any_replica_success(integration, id, couchbase::lookup_in_specs::exists("array"));
1352
+ }
1353
+
1354
+ SECTION("array index get")
1355
+ {
1356
+ assert_single_lookup_any_replica_success(integration, id, couchbase::lookup_in_specs::get("array[0]"), "1");
1357
+ }
1358
+
1359
+ SECTION("array index exists")
1360
+ {
1361
+ assert_single_lookup_any_replica_success(integration, id, couchbase::lookup_in_specs::exists("array[0]"));
1362
+ }
1363
+
1364
+ SECTION("non existent path get")
1365
+ {
1366
+ assert_single_lookup_any_replica_error(integration,
1367
+ id,
1368
+ couchbase::lookup_in_specs::get("non-exist"),
1369
+ couchbase::key_value_status_code::subdoc_path_not_found,
1370
+ couchbase::errc::key_value::path_not_found);
1371
+ }
1372
+
1373
+ SECTION("non existent path exists")
1374
+ {
1375
+ assert_single_lookup_any_replica_error(integration,
1376
+ id,
1377
+ couchbase::lookup_in_specs::exists("non-exist"),
1378
+ couchbase::key_value_status_code::subdoc_path_not_found,
1379
+ couchbase::errc::key_value::path_not_found);
1380
+ }
1381
+
1382
+ SECTION("non existent doc")
1383
+ {
1384
+ couchbase::core::document_id missing_id{ integration.ctx.bucket, "_default", "_default", "missing_key" };
1385
+
1386
+ SECTION("non existent doc get")
1387
+ {
1388
+ couchbase::core::operations::lookup_in_any_replica_request req{ missing_id };
1389
+ req.specs =
1390
+ couchbase::lookup_in_specs{
1391
+ couchbase::lookup_in_specs::get("non-exist"),
1392
+ }
1393
+ .specs();
1394
+ auto resp = test::utils::execute(integration.cluster, req);
1395
+ REQUIRE(resp.ctx.ec() == couchbase::errc::key_value::document_irretrievable);
1396
+ REQUIRE(resp.fields.empty());
1397
+ }
1398
+
1399
+ SECTION("non existent doc exists")
1400
+ {
1401
+ couchbase::core::operations::lookup_in_any_replica_request req{ missing_id };
1402
+ req.specs =
1403
+ couchbase::lookup_in_specs{
1404
+ couchbase::lookup_in_specs::exists("non-exist"),
1405
+ }
1406
+ .specs();
1407
+ auto resp = test::utils::execute(integration.cluster, req);
1408
+ REQUIRE(resp.ctx.ec() == couchbase::errc::key_value::document_irretrievable);
1409
+ REQUIRE(resp.fields.empty());
1410
+ }
1411
+ }
1412
+
1413
+ SECTION("non json")
1414
+ {
1415
+ couchbase::core::document_id non_json_id{ integration.ctx.bucket, "_default", "_default", test::utils::uniq_id("non_json") };
1416
+ auto non_json_doc = couchbase::core::utils::to_binary("string");
1417
+
1418
+ {
1419
+ couchbase::core::operations::insert_request req{ non_json_id, non_json_doc };
1420
+ auto resp = test::utils::execute(integration.cluster, req);
1421
+ REQUIRE_SUCCESS(resp.ctx.ec());
1422
+ }
1423
+
1424
+ SECTION("non json get")
1425
+ {
1426
+ if (integration.cluster_version().is_mock()) {
1427
+ SKIP("GOCAVES does not handle subdocument operations for non-JSON documents. See "
1428
+ "https://github.com/couchbaselabs/gocaves/issues/103");
1429
+ }
1430
+ assert_single_lookup_any_replica_error(integration,
1431
+ non_json_id,
1432
+ couchbase::lookup_in_specs::get("non-exist"),
1433
+ couchbase::key_value_status_code::subdoc_doc_not_json,
1434
+ couchbase::errc::key_value::document_not_json);
1435
+ }
1436
+
1437
+ SECTION("non json exists")
1438
+ {
1439
+ if (integration.cluster_version().is_mock()) {
1440
+ SKIP("GOCAVES does not handle subdocument operations for non-JSON documents. See "
1441
+ "https://github.com/couchbaselabs/gocaves/issues/103");
1442
+ }
1443
+ assert_single_lookup_any_replica_error(integration,
1444
+ non_json_id,
1445
+ couchbase::lookup_in_specs::exists("non-exist"),
1446
+ couchbase::key_value_status_code::subdoc_doc_not_json,
1447
+ couchbase::errc::key_value::document_not_json);
1448
+ }
1449
+ }
1450
+
1451
+ SECTION("invalid path")
1452
+ {
1453
+ std::vector<std::string> invalid_paths = { "invalid..path", "invalid[-2]" };
1454
+ for (const auto& path : invalid_paths) {
1455
+ if (integration.cluster_version().is_mock()) {
1456
+ assert_single_lookup_any_replica_error(integration,
1457
+ id,
1458
+ couchbase::lookup_in_specs::get(path),
1459
+ couchbase::key_value_status_code::subdoc_path_not_found,
1460
+ couchbase::errc::key_value::path_not_found);
1461
+ } else {
1462
+ assert_single_lookup_any_replica_error(integration,
1463
+ id,
1464
+ couchbase::lookup_in_specs::get(path),
1465
+ couchbase::key_value_status_code::subdoc_path_invalid,
1466
+ couchbase::errc::key_value::path_invalid);
1467
+ }
1468
+ }
1469
+ }
1470
+
1471
+ SECTION("negative paths")
1472
+ {
1473
+ assert_single_lookup_any_replica_success(integration, id, couchbase::lookup_in_specs::get("array[-1][-1][-1]"), "300");
1474
+ }
1475
+
1476
+ SECTION("nested arrays")
1477
+ {
1478
+ assert_single_lookup_any_replica_success(integration, id, couchbase::lookup_in_specs::get("array[4][3][2]"), "300");
1479
+ }
1480
+
1481
+ SECTION("path mismatch")
1482
+ {
1483
+ assert_single_lookup_any_replica_error(integration,
1484
+ id,
1485
+ couchbase::lookup_in_specs::get("array.key"),
1486
+ couchbase::key_value_status_code::subdoc_path_mismatch,
1487
+ couchbase::errc::key_value::path_mismatch);
1488
+ }
1489
+
1490
+ SECTION("too many specs")
1491
+ {
1492
+ couchbase::core::operations::lookup_in_any_replica_request req{ id };
1493
+ auto specs = couchbase::lookup_in_specs{};
1494
+
1495
+ for (int i = 0; i < 17; i++) {
1496
+ specs.push_back(couchbase::lookup_in_specs::get("dictkey"));
1497
+ }
1498
+ req.specs = specs.specs();
1499
+
1500
+ auto resp = test::utils::execute(integration.cluster, req);
1501
+ REQUIRE(resp.ctx.ec() == couchbase::errc::common::invalid_argument);
1502
+ REQUIRE(resp.fields.empty());
1503
+ }
1504
+
1505
+ SECTION("public API")
1506
+ {
1507
+ auto collection = couchbase::cluster(integration.cluster).bucket(integration.ctx.bucket).scope("_default").collection("_default");
1508
+
1509
+ SECTION("lookup in any replica")
1510
+ {
1511
+ auto specs = couchbase::lookup_in_specs{ couchbase::lookup_in_specs::get("dictkey"),
1512
+ couchbase::lookup_in_specs::exists("array"),
1513
+ couchbase::lookup_in_specs::count("array") };
1514
+ auto [ctx, result] = collection.lookup_in_any_replica(key, specs).get();
1515
+ REQUIRE_SUCCESS(ctx.ec());
1516
+ REQUIRE(!result.cas().empty());
1517
+ REQUIRE("dictval" == result.content_as<std::string>(0));
1518
+ REQUIRE(result.exists("array"));
1519
+ REQUIRE(5 == result.content_as<int>(2));
1520
+ }
1521
+
1522
+ SECTION("missing document")
1523
+ {
1524
+ auto specs = couchbase::lookup_in_specs{
1525
+ couchbase::lookup_in_specs::get("non-exists"),
1526
+ };
1527
+ auto [ctx, result] = collection.lookup_in_any_replica("missing-key", specs).get();
1528
+ REQUIRE(ctx.ec() == couchbase::errc::key_value::document_irretrievable);
1529
+ REQUIRE(result.cas().empty());
1530
+ }
1531
+
1532
+ SECTION("too many specs")
1533
+ {
1534
+ auto specs = couchbase::lookup_in_specs{};
1535
+
1536
+ for (int i = 0; i < 17; i++) {
1537
+ specs.push_back(couchbase::lookup_in_specs::get("dictkey"));
1538
+ }
1539
+ auto [ctx, result] = collection.lookup_in_any_replica(key, specs).get();
1540
+ REQUIRE(ctx.ec() == couchbase::errc::common::invalid_argument);
1541
+ REQUIRE(result.cas().empty());
1542
+ }
1543
+ }
1544
+ }
1545
+
1546
+ TEST_CASE("integration: public API lookup in per-spec errors", "[integration]")
1547
+ {
1548
+ test::utils::integration_test_guard integration;
1549
+
1550
+ auto collection = couchbase::cluster(integration.cluster).bucket(integration.ctx.bucket).scope("_default").collection("_default");
1551
+
1552
+ auto key = test::utils::uniq_id("lookup_in_path_invalid");
1553
+ {
1554
+ auto value_json = couchbase::core::utils::json::parse(R"({"dictkey":"dictval","array":[1,2,3,4,[10,20,30,[100,200,300]]]})");
1555
+ auto [ctx, result] = collection.upsert(key, value_json).get();
1556
+ REQUIRE_SUCCESS(ctx.ec());
1557
+ }
1558
+
1559
+ SECTION("path invalid")
1560
+ {
1561
+ auto specs = couchbase::lookup_in_specs{
1562
+ couchbase::lookup_in_specs::get("..dictkey"),
1563
+ };
1564
+ auto [ctx, result] = collection.lookup_in(key, specs).get();
1565
+ std::error_code ec{};
1566
+ try {
1567
+ std::ignore = result.content_as<std::string>(0);
1568
+ } catch (std::system_error& exc) {
1569
+ ec = exc.code();
1570
+ }
1571
+ REQUIRE(ec == couchbase::errc::key_value::path_invalid);
1572
+
1573
+ ec.clear();
1574
+ try {
1575
+ std::ignore = result.exists(0);
1576
+ } catch (std::system_error& exc) {
1577
+ ec = exc.code();
1578
+ }
1579
+ REQUIRE(ec == couchbase::errc::key_value::path_invalid);
1580
+ }
1581
+
1582
+ SECTION("path mismatch")
1583
+ {
1584
+ auto specs = couchbase::lookup_in_specs{
1585
+ couchbase::lookup_in_specs::count("dictkey"),
1586
+ };
1587
+ auto [ctx, result] = collection.lookup_in(key, specs).get();
1588
+
1589
+ std::error_code ec{};
1590
+ try {
1591
+ std::ignore = result.content_as<std::string>(0);
1592
+ } catch (std::system_error& exc) {
1593
+ ec = exc.code();
1594
+ }
1595
+ REQUIRE(ec == couchbase::errc::key_value::path_mismatch);
1596
+
1597
+ ec.clear();
1598
+ try {
1599
+ std::ignore = result.exists(0);
1600
+ } catch (std::system_error& exc) {
1601
+ ec = exc.code();
1602
+ }
1603
+ REQUIRE(ec == couchbase::errc::key_value::path_mismatch);
1604
+ }
1605
+
1606
+ SECTION("path not found")
1607
+ {
1608
+ auto specs = couchbase::lookup_in_specs{
1609
+ couchbase::lookup_in_specs::get("dictkey2"),
1610
+ };
1611
+ auto [ctx, result] = collection.lookup_in(key, specs).get();
1612
+
1613
+ std::error_code ec{};
1614
+ try {
1615
+ std::ignore = result.content_as<std::string>(0);
1616
+ } catch (std::system_error& exc) {
1617
+ ec = exc.code();
1618
+ }
1619
+ REQUIRE(ec == couchbase::errc::key_value::path_not_found);
1620
+
1621
+ ec.clear();
1622
+ try {
1623
+ auto exists = result.exists(0);
1624
+ REQUIRE_FALSE(exists);
1625
+ } catch (std::system_error& exc) {
1626
+ ec = exc.code();
1627
+ }
1628
+ REQUIRE_SUCCESS(ec);
1629
+ }
1630
+ }