couchbase 4.2.6 → 4.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ }