From b471620a2490a5780a1686c1e8749a9e0eba4461 Mon Sep 17 00:00:00 2001 From: Harpreet Sangar Date: Mon, 17 Jun 2024 19:32:54 +0530 Subject: [PATCH] Remove values from `reference_index` and `object_array_reference_index` in `Index::remove_field`. (#1787) --- src/index.cpp | 32 ++++++++- test/collection_join_test.cpp | 132 +++++++++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 4 deletions(-) diff --git a/src/index.cpp b/src/index.cpp index b645a836..e655123c 100644 --- a/src/index.cpp +++ b/src/index.cpp @@ -6806,9 +6806,27 @@ void Index::remove_field(uint32_t seq_id, nlohmann::json& document, const std::s } } } else if(search_field.is_int64()) { - const std::vector& values = search_field.is_single_integer() ? - std::vector{document[field_name].get()} : - document[field_name].get>(); + std::vector values; + std::vector> object_array_reference_values; + + if (search_field.is_array() && search_field.nested && search_field.is_reference_helper) { + for (const auto &pair: document[field_name]) { + if (!pair.is_array() || pair.size() != 2 || !pair[0].is_number_unsigned() || + !pair[1].is_number_unsigned()) { + LOG(ERROR) << "`" + field_name + "` object array reference helper field has wrong value `" + + pair.dump() + "`."; + continue; + } + + object_array_reference_values.emplace_back(seq_id, pair[0]); + values.emplace_back(pair[1]); + } + } else { + values = search_field.is_single_integer() ? + std::vector{document[field_name].get()} : + document[field_name].get>(); + } + for(int64_t value: values) { if (search_field.range_index) { auto trie = range_index.at(field_name); @@ -6821,6 +6839,14 @@ void Index::remove_field(uint32_t seq_id, nlohmann::json& document, const std::s if(search_field.facet) { remove_facet_token(search_field, search_index, std::to_string(value), seq_id); } + + if (reference_index.count(field_name) != 0) { + reference_index[field_name]->remove(value, seq_id); + } + } + + for (auto const& pair: object_array_reference_values) { + object_array_reference_index[field_name]->erase(pair); } } else if(search_field.num_dim) { if(!is_update) { diff --git a/test/collection_join_test.cpp b/test/collection_join_test.cpp index 67db7a29..81f17f19 100644 --- a/test/collection_join_test.cpp +++ b/test/collection_join_test.cpp @@ -1210,6 +1210,88 @@ TEST_F(CollectionJoinTest, UpdateDocumentHavingReferenceField) { doc = coll->get("4").get(); ASSERT_EQ(1, doc.count("product_id_sequence_id")); ASSERT_EQ(1, doc["product_id_sequence_id"]); + + schema_json = + R"({ + "name": "Users", + "fields": [ + {"name": "name", "type": "string"} + ] + })"_json; + documents = { + R"({ + "id": "user_a", + "name": "Joe" + })"_json, + R"({ + "id": "user_b", + "name": "Dan" + })"_json, + }; + collection_create_op = collectionManager.create_collection(schema_json); + ASSERT_TRUE(collection_create_op.ok()); + for (auto const &json: documents) { + auto add_op = collection_create_op.get()->add(json.dump()); + ASSERT_TRUE(add_op.ok()); + } + + schema_json = + R"({ + "name": "Repos", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "stargazers", "type": "string[]", "reference": "Users.id"} + ] + })"_json; + documents = { + R"({ + "id": "repo_a", + "name": "Typesense", + "stargazers": ["user_a", "user_b"] + })"_json, + }; + collection_create_op = collectionManager.create_collection(schema_json); + ASSERT_TRUE(collection_create_op.ok()); + for (auto const &json: documents) { + auto add_op = collection_create_op.get()->add(json.dump()); + ASSERT_TRUE(add_op.ok()); + } + + req_params = { + {"collection", "Repos"}, + {"q", "*"}, + {"include_fields", "$Users(name)"} + }; + search_op = collectionManager.do_search(req_params, embedded_params, json_res, now_ts); + ASSERT_TRUE(search_op.ok()); + + res_obj = nlohmann::json::parse(json_res); + ASSERT_EQ(1, res_obj["found"].get()); + ASSERT_EQ(1, res_obj["hits"].size()); + ASSERT_EQ(2, res_obj["hits"][0]["document"]["Users"].size()); + ASSERT_EQ("Joe", res_obj["hits"][0]["document"]["Users"][0]["name"]); + ASSERT_EQ("Dan", res_obj["hits"][0]["document"]["Users"][1]["name"]); + + auto json = R"({ + "stargazers": ["user_b"] + })"_json; + + auto add_op = collection_create_op.get()->add(json.dump(), index_operation_t::UPDATE, "repo_a", DIRTY_VALUES::REJECT); + ASSERT_TRUE(add_op.ok()); + + req_params = { + {"collection", "Repos"}, + {"q", "*"}, + {"include_fields", "$Users(name)"} + }; + search_op = collectionManager.do_search(req_params, embedded_params, json_res, now_ts); + ASSERT_TRUE(search_op.ok()); + + res_obj = nlohmann::json::parse(json_res); + ASSERT_EQ(1, res_obj["found"].get()); + ASSERT_EQ(1, res_obj["hits"].size()); + ASSERT_EQ(1, res_obj["hits"][0]["document"]["Users"].size()); + ASSERT_EQ("Dan", res_obj["hits"][0]["document"]["Users"][0]["name"]); } TEST_F(CollectionJoinTest, FilterByReference_SingleMatch) { @@ -4054,7 +4136,6 @@ TEST_F(CollectionJoinTest, FilterByObjectReferenceField) { {"include_fields", "$Portions(*, strategy:merge)"} }; search_op_bool = collectionManager.do_search(req_params, embedded_params, json_res, now_ts); - LOG(INFO) << search_op_bool.error(); ASSERT_TRUE(search_op_bool.ok()); res_obj = nlohmann::json::parse(json_res); @@ -4083,6 +4164,55 @@ TEST_F(CollectionJoinTest, FilterByObjectReferenceField) { ASSERT_EQ(1 , res_obj["hits"][0]["document"]["portions"][2].at("count")); + ASSERT_EQ("Bread", res_obj["hits"][1]["document"]["name"]); + ASSERT_EQ(1, res_obj["hits"][1]["document"].count("portions")); + ASSERT_EQ(1, res_obj["hits"][1]["document"]["portions"].size()); + + ASSERT_EQ(5, res_obj["hits"][1]["document"]["portions"][0].size()); + ASSERT_EQ("portion_a", res_obj["hits"][1]["document"]["portions"][0].at("portion_id")); + ASSERT_EQ(500 , res_obj["hits"][1]["document"]["portions"][0].at("quantity")); + ASSERT_EQ("g", res_obj["hits"][1]["document"]["portions"][0].at("unit")); + ASSERT_EQ(10 , res_obj["hits"][1]["document"]["portions"][0].at("count")); + + auto doc = R"({ + "name": "Milk", + "portions": [ + { + "portion_id": "portion_c", + "count": 1 + } + ] + })"_json; + + auto add_op = collectionManager.get_collection_unsafe("Foods")->add(doc.dump(), index_operation_t::UPDATE, "1", + DIRTY_VALUES::REJECT); + ASSERT_TRUE(add_op.ok()); + + req_params = { + {"collection", "Foods"}, + {"q", "*"}, + {"include_fields", "$Portions(*, strategy:merge)"} + }; + search_op_bool = collectionManager.do_search(req_params, embedded_params, json_res, now_ts); + ASSERT_TRUE(search_op_bool.ok()); + + res_obj = nlohmann::json::parse(json_res); + ASSERT_EQ(2, res_obj["found"].get()); + ASSERT_EQ(2, res_obj["hits"].size()); + ASSERT_EQ(3, res_obj["hits"][0]["document"].size()); + ASSERT_EQ(1, res_obj["hits"][0]["document"].count("name")); + + ASSERT_EQ("Milk", res_obj["hits"][0]["document"]["name"]); + ASSERT_EQ(1, res_obj["hits"][0]["document"].count("portions")); + ASSERT_EQ(1, res_obj["hits"][0]["document"]["portions"].size()); + + ASSERT_EQ(5, res_obj["hits"][0]["document"]["portions"][0].size()); + ASSERT_EQ("portion_c", res_obj["hits"][0]["document"]["portions"][0].at("portion_id")); + ASSERT_EQ(500 , res_obj["hits"][0]["document"]["portions"][0].at("quantity")); + ASSERT_EQ("ml", res_obj["hits"][0]["document"]["portions"][0].at("unit")); + ASSERT_EQ(1 , res_obj["hits"][0]["document"]["portions"][0].at("count")); + + ASSERT_EQ("Bread", res_obj["hits"][1]["document"]["name"]); ASSERT_EQ(1, res_obj["hits"][1]["document"].count("portions")); ASSERT_EQ(1, res_obj["hits"][1]["document"]["portions"].size());