Cascade delete references.

This commit is contained in:
Harpreet Sangar 2023-07-25 17:51:17 +05:30
parent 150b05e814
commit 825b61951a
4 changed files with 157 additions and 4 deletions

View File

@ -133,6 +133,11 @@ private:
/// "field name" -> reference_pair(referenced_collection_name, referenced_field_name)
spp::sparse_hash_map<std::string, reference_pair> reference_fields;
/// Contains the info where the current collection is referenced.
/// Useful to perform operations such as cascading delete.
/// collection_name -> field_name
spp::sparse_hash_map<std::string, std::string> referenced_in;
// Keep index as the last field since it is initialized in the constructor via init_index(). Add a new field before it.
Index* index;

View File

@ -3423,6 +3423,23 @@ void Collection::remove_document(const nlohmann::json & document, const uint32_t
store->remove(get_doc_id_key(id));
store->remove(get_seq_id_key(seq_id));
}
if (referenced_in.empty()) {
return;
}
CollectionManager& collectionManager = CollectionManager::get_instance();
// Cascade delete all the references.
for (const auto &item: referenced_in) {
auto ref_coll = collectionManager.get_collection(item.first);
if (ref_coll != nullptr) {
filter_result_t filter_result;
ref_coll->get_filter_ids(item.second + ":=" + id, filter_result);
for (uint32_t i = 0; i < filter_result.count; i++) {
ref_coll->remove(std::to_string(filter_result.docs[i]));
}
}
}
}
Option<std::string> Collection::remove(const std::string & id, const bool remove_from_store) {
@ -4684,10 +4701,17 @@ Index* Collection::init_index() {
if(!field.reference.empty()) {
auto dot_index = field.reference.find('.');
auto collection_name = field.reference.substr(0, dot_index);
auto field_name = field.reference.substr(dot_index + 1);
auto ref_coll_name = field.reference.substr(0, dot_index);
auto ref_field_name = field.reference.substr(dot_index + 1);
reference_fields.emplace(field.name, reference_pair(collection_name, field_name));
reference_fields.emplace(field.name, reference_pair(ref_coll_name, ref_field_name));
auto& collectionManager = CollectionManager::get_instance();
auto ref_coll = collectionManager.get_collection(ref_coll_name);
if (ref_coll != nullptr) {
// Passing reference helper field helps perform operation on doc_id instead of field value.
ref_coll->referenced_in.emplace(name, field.name + REFERENCE_HELPER_FIELD_SUFFIX);
}
}
}

View File

@ -21,7 +21,6 @@
#include <or_iterator.h>
#include <timsort.hpp>
#include "logger.h"
#include <collection_manager.h>
#include "validator.h"
#define RETURN_CIRCUIT_BREAKER if((std::chrono::duration_cast<std::chrono::microseconds>( \

View File

@ -1430,3 +1430,128 @@ TEST_F(CollectionJoinTest, IncludeExcludeFieldsByReference_SingleMatch) {
ASSERT_EQ(1, res_obj["hits"][0]["document"].count("product_price"));
ASSERT_EQ(73.5, res_obj["hits"][0]["document"].at("product_price"));
}
TEST_F(CollectionJoinTest, CascadeDeletion) {
auto schema_json =
R"({
"name": "Products",
"fields": [
{"name": "product_id", "type": "string"},
{"name": "product_name", "type": "string", "infix": true},
{"name": "product_description", "type": "string"}
]
})"_json;
std::vector<nlohmann::json> documents = {
R"({
"product_id": "product_a",
"product_name": "shampoo",
"product_description": "Our new moisturizing shampoo is perfect for those with dry or damaged hair."
})"_json,
R"({
"product_id": "product_b",
"product_name": "soap",
"product_description": "Introducing our all-natural, organic soap bar made with essential oils and botanical ingredients."
})"_json
};
auto 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": "Customers",
"fields": [
{"name": "customer_id", "type": "string"},
{"name": "customer_name", "type": "string"},
{"name": "product_price", "type": "float"},
{"name": "product_id", "type": "string", "reference": "Products.product_id"}
]
})"_json;
documents = {
R"({
"customer_id": "customer_a",
"customer_name": "Joe",
"product_price": 143,
"product_id": "product_a"
})"_json,
R"({
"customer_id": "customer_a",
"customer_name": "Joe",
"product_price": 73.5,
"product_id": "product_b"
})"_json,
R"({
"customer_id": "customer_b",
"customer_name": "Dan",
"product_price": 75,
"product_id": "product_a"
})"_json,
R"({
"customer_id": "customer_b",
"customer_name": "Dan",
"product_price": 140,
"product_id": "product_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());
}
std::map<std::string, std::string> req_params = {
{"collection", "Customers"},
{"q", "*"},
};
nlohmann::json embedded_params;
std::string json_res;
auto now_ts = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
auto search_op = collectionManager.do_search(req_params, embedded_params, json_res, now_ts);
nlohmann::json res_obj = nlohmann::json::parse(json_res);
ASSERT_EQ(4, res_obj["found"].get<size_t>());
ASSERT_EQ("product_b", res_obj["hits"][0]["document"].at("product_id"));
ASSERT_EQ("product_a", res_obj["hits"][1]["document"].at("product_id"));
ASSERT_EQ("product_b", res_obj["hits"][0]["document"].at("product_id"));
ASSERT_EQ("product_a", res_obj["hits"][1]["document"].at("product_id"));
req_params = {
{"collection", "Products"},
{"q", "*"},
};
search_op = collectionManager.do_search(req_params, embedded_params, json_res, now_ts);
res_obj = nlohmann::json::parse(json_res);
ASSERT_EQ(2, res_obj["found"].get<size_t>());
ASSERT_EQ("product_b", res_obj["hits"][0]["document"].at("product_id"));
ASSERT_EQ("product_a", res_obj["hits"][1]["document"].at("product_id"));
collectionManager.get_collection_unsafe("Products")->remove("0");
req_params = {
{"collection", "Products"},
{"q", "*"},
};
search_op = collectionManager.do_search(req_params, embedded_params, json_res, now_ts);
res_obj = nlohmann::json::parse(json_res);
ASSERT_EQ(1, res_obj["found"].get<size_t>());
ASSERT_EQ("product_b", res_obj["hits"][0]["document"].at("product_id"));
req_params = {
{"collection", "Customers"},
{"q", "*"},
};
search_op = collectionManager.do_search(req_params, embedded_params, json_res, now_ts);
res_obj = nlohmann::json::parse(json_res);
ASSERT_EQ(2, res_obj["found"].get<size_t>());
ASSERT_EQ("product_b", res_obj["hits"][0]["document"].at("product_id"));
ASSERT_EQ("product_b", res_obj["hits"][1]["document"].at("product_id"));
}