adding sort by alpha

This commit is contained in:
krunal 2023-08-10 16:10:28 +05:30
parent e70e8d070a
commit cfcac97fdc
6 changed files with 241 additions and 65 deletions

View File

@ -72,7 +72,7 @@ private:
};
struct facet_doc_ids_list_t {
tsl::htrie_map<char, facet_id_seq_ids_t> fvalue_seq_ids;
std::map<std::string, facet_id_seq_ids_t> fvalue_seq_ids;
std::list<facet_count_t> counts;
posting_list_t* seq_id_hashes = nullptr;
@ -89,8 +89,8 @@ private:
~facet_doc_ids_list_t() {
for(auto it = fvalue_seq_ids.begin(); it != fvalue_seq_ids.end(); ++it) {
if(it.value().seq_ids) {
ids_t::destroy_list(it.value().seq_ids);
if(it->second.seq_ids) {
ids_t::destroy_list(it->second.seq_ids);
}
}
@ -113,8 +113,8 @@ public:
~facet_index_t();
void insert(const std::string& field_name, bool is_string,
std::unordered_map<facet_value_id_t, std::vector<uint32_t>, facet_value_id_t::Hash>& fvalue_to_seq_ids,
void insert(const std::string& field_name, std::unordered_map<facet_value_id_t,
std::vector<uint32_t>, facet_value_id_t::Hash>& fvalue_to_seq_ids,
std::unordered_map<uint32_t, std::vector<facet_value_id_t>>& seq_id_to_fvalues);
void erase(const std::string& field_name);
@ -127,7 +127,7 @@ public:
size_t intersect(const std::string& val, const uint32_t* result_ids, size_t result_id_len,
size_t max_facet_count, std::map<std::string, uint32_t>& found,
bool is_wildcard_no_filter_query);
bool is_wildcard_no_filter_query, const std::string& sort_order = "");
size_t get_facet_indexes(const std::string& field,
std::map<uint32_t, std::vector<uint32_t>>& seqid_countIndexes);

View File

@ -377,8 +377,7 @@ private:
size_t group_limit, const std::vector<std::string>& group_by_fields,
const uint32_t* result_ids, size_t results_size,
int max_facet_count, bool is_wildcard_query, bool no_filters_provided,
facet_index_type_t facet_index_type
) const;
facet_index_type_t facet_index_type, const facet_sort_by& facet_sort_params={}) const;
bool static_filter_query_eval(const override_t* override, std::vector<std::string>& tokens,
filter_node_t*& filter_tree_root) const;
@ -633,7 +632,7 @@ public:
// Public operations
Option<bool> run_search(search_args* search_params, const std::string& collection_name,
facet_index_type_t facet_index_type);
facet_index_type_t facet_index_type, const facet_sort_by& facet_sort_params={});
Option<bool> search(std::vector<query_tokens_t>& field_query_tokens, const std::vector<search_field_t>& the_fields,
const text_match_type_t match_type,
@ -658,7 +657,8 @@ public:
const size_t max_extra_suffix, const size_t facet_query_num_typos,
const bool filter_curated_hits, enable_t split_join_tokens,
const vector_query_t& vector_query, size_t facet_sample_percent, size_t facet_sample_threshold,
const std::string& collection_name, facet_index_type_t facet_index_type = DETECT) const;
const std::string& collection_name, facet_index_type_t facet_index_type = DETECT,
const facet_sort_by& facet_sort_params={}) const;
void remove_field(uint32_t seq_id, const nlohmann::json& document, const std::string& field_name);

View File

@ -1631,7 +1631,7 @@ Option<nlohmann::json> Collection::search(std::string raw_query,
std::unique_ptr<search_args> search_params_guard(search_params);
auto search_op = index->run_search(search_params, name, facet_index_type);
auto search_op = index->run_search(search_params, name, facet_index_type, facet_sort_param);
// filter_tree_root might be updated in Index::static_filter_query_eval.
filter_tree_root_guard.release();
@ -2114,7 +2114,7 @@ Option<nlohmann::json> Collection::search(std::string raw_query,
}
std::unordered_map<std::string, size_t> ftoken_pos;
std::vector<string>& ftokens = a_facet.hash_tokens[kv.first];
std::vector<std::string>& ftokens = a_facet.hash_tokens[kv.first];
//LOG(INFO) << "working on hash_tokens for hash " << kv.first << " with size " << ftokens.size();
for(size_t ti = 0; ti < ftokens.size(); ti++) {
if(the_field.is_bool()) {
@ -2187,9 +2187,19 @@ Option<nlohmann::json> Collection::search(std::string raw_query,
}
}
if(!facet_sort_param.param.empty()) {
if(facet_sort_param.param == "alpha") {
bool is_asc = facet_sort_param.order == "asc";
std::stable_sort(facet_values.begin(), facet_values.end(), [&](const auto& fv1, const auto& fv2) {
std::stable_sort(facet_values.begin(), facet_values.end(),
[&](const auto& fv1, const auto& fv2) {
if(is_asc) {
return fv1.value < fv2.value;
}
return fv1.value > fv2.value;
});
} else if(!facet_sort_param.param.empty()) {
bool is_asc = facet_sort_param.order == "asc";
std::stable_sort(facet_values.begin(), facet_values.end(),
[&](const auto& fv1, const auto& fv2) {
if(is_asc) {
return fv1.sort_field_val < fv2.sort_field_val;
}
@ -2199,7 +2209,6 @@ Option<nlohmann::json> Collection::search(std::string raw_query,
if(facet_values.size() > max_facet_values) {
facet_values.erase(facet_values.begin() + max_facet_values, facet_values.end());
}
} else {
std::stable_sort(facet_values.begin(), facet_values.end(), Collection::facet_count_str_compare);
}
@ -5006,20 +5015,24 @@ Option<bool> Collection::parse_facet(const std::string& facet_field, std::vector
}
Option<bool> Collection::validate_facet_sort_by_field(const facet_sort_by& facet_sort_params) const {
if (search_schema.count(facet_sort_params.param) == 0) {
std::string error = "Could not find a facet field named `" + facet_sort_params.param + "` in the schema.";
return Option<bool>(404, error);
}
if(facet_sort_params.param == "alpha") {
//sort param can be either alphabetical sort or sort by other field
} else {
if (search_schema.count(facet_sort_params.param) == 0) {
std::string error = "Could not find a facet field named `" + facet_sort_params.param + "` in the schema.";
return Option<bool>(404, error);
}
const field &a_field = search_schema.at(facet_sort_params.param);
if (!a_field.nested) {
std::string error = "Field for `facet_sort_by` should be nested from same facet field";
return Option<bool>(400, error);
}
const field &a_field = search_schema.at(facet_sort_params.param);
if (!a_field.nested) {
std::string error = "Field for `facet_sort_by` should be nested from same facet field";
return Option<bool>(400, error);
}
if(a_field.is_string()) {
std::string error = "Field for `facet_sort_by` should not be string";
return Option<bool>(400, error);
if (a_field.is_string()) {
std::string error = "Field for `facet_sort_by` should not be string";
return Option<bool>(400, error);
}
}
if ((facet_sort_params.order != "asc") && (facet_sort_params.order != "desc")) {

View File

@ -11,8 +11,8 @@ void facet_index_t::initialize(const std::string& field) {
}
}
void facet_index_t::insert(const std::string& field_name, bool is_string,
std::unordered_map<facet_value_id_t, std::vector<uint32_t>, facet_value_id_t::Hash>& fvalue_to_seq_ids,
void facet_index_t::insert(const std::string& field_name,std::unordered_map<facet_value_id_t,
std::vector<uint32_t>, facet_value_id_t::Hash>& fvalue_to_seq_ids,
std::unordered_map<uint32_t, std::vector<facet_value_id_t>>& seq_id_to_fvalues) {
const auto facet_field_map_it = facet_field_map.find(field_name);
@ -21,7 +21,7 @@ void facet_index_t::insert(const std::string& field_name, bool is_string,
}
auto& facet_index = facet_field_map_it->second;
tsl::htrie_map<char, facet_id_seq_ids_t>& fvalue_index = facet_index.fvalue_seq_ids;
auto& fvalue_index = facet_index.fvalue_seq_ids;
auto fhash_index = facet_index.seq_id_hashes;
for(const auto& seq_id_fvalues: seq_id_to_fvalues) {
@ -35,7 +35,7 @@ void facet_index_t::insert(const std::string& field_name, bool is_string,
if(fvalue.facet_id == UINT32_MAX) {
// float, int32 & bool will provide facet_id as their own numerical values
facet_id = (fvalue_index_it == fvalue_index.end()) ? ++next_facet_id : fvalue_index_it->facet_id;
facet_id = (fvalue_index_it == fvalue_index.end()) ? ++next_facet_id : fvalue_index_it->second.facet_id;
}
real_facet_ids.push_back(facet_id);
@ -61,13 +61,13 @@ void facet_index_t::insert(const std::string& field_name, bool is_string,
fvalue_index.emplace(fvalue.facet_value, fis);
} else if(facet_index.has_value_index) {
for(const auto id : seq_ids) {
ids_t::upsert(fvalue_index_it->seq_ids, id);
ids_t::upsert(fvalue_index_it->second.seq_ids, id);
}
auto facet_count_it = fvalue_index_it->facet_count_it;
auto facet_count_it = fvalue_index_it->second.facet_count_it;
if(facet_count_it->facet_id == facet_id) {
facet_count_it->count = ids_t::num_ids(fvalue_index_it->seq_ids);
facet_count_it->count = ids_t::num_ids(fvalue_index_it->second.seq_ids);
auto curr = facet_count_it;
while (curr != count_list.begin() && std::prev(curr)->count < curr->count) {
count_list.splice(curr, count_list, std::prev(curr)); // swaps list nodes
@ -107,20 +107,22 @@ void facet_index_t::remove(const std::string& field_name, const uint32_t seq_id)
std::vector<std::string> dead_fvalues;
for(auto facet_ids_seq_ids = facet_index_map.begin(); facet_ids_seq_ids != facet_index_map.end(); facet_ids_seq_ids++) {
void*& ids = facet_ids_seq_ids.value().seq_ids;
void*& ids = facet_ids_seq_ids->second.seq_ids;
if(ids && ids_t::contains(ids, seq_id)) {
ids_t::erase(ids, seq_id);
auto& count_list = facet_field_it->second.counts;
auto key = facet_ids_seq_ids.key();
auto& facet_id_seq_ids = facet_ids_seq_ids.value();
facet_ids_seq_ids.value().facet_count_it->count--;
auto key = facet_ids_seq_ids->first;
auto& facet_id_seq_ids = facet_ids_seq_ids->second;
facet_ids_seq_ids->second.facet_count_it->count--;
if(ids_t::num_ids(ids) == 0) {
ids_t::destroy_list(ids);
std::string dead_fvalue;
facet_ids_seq_ids.key(dead_fvalue);
dead_fvalues.push_back(dead_fvalue);
count_list.erase(facet_ids_seq_ids.value().facet_count_it);
count_list.erase(facet_ids_seq_ids->second.facet_count_it);
auto node = facet_index_map.extract(facet_ids_seq_ids->first);
node.key() = dead_fvalue;
facet_index_map.insert(std::move(node));
}
}
}
@ -147,7 +149,7 @@ size_t facet_index_t::get_facet_count(const std::string& field_name) {
//returns the count of matching seq_ids from result array
size_t facet_index_t::intersect(const std::string& field, const uint32_t* result_ids, size_t result_ids_len,
size_t max_facet_count, std::map<std::string, uint32_t>& found,
bool is_wildcard_no_filter_query) {
bool is_wildcard_no_filter_query, const std::string& sort_order) {
//LOG (INFO) << "intersecting field " << field;
const auto& facet_field_it = facet_field_map.find(field);
@ -165,27 +167,55 @@ size_t facet_index_t::intersect(const std::string& field, const uint32_t* result
size_t max_facets = is_wildcard_no_filter_query ? std::min((size_t)max_facet_count, counter_list.size()) :
std::min((size_t)2 * max_facet_count, counter_list.size());
for(const auto& facet_count : counter_list) {
//LOG(INFO) << "checking ids in facet_value " << facet_count.facet_value << " having total count "
// << facet_count.count << ", is_wildcard_no_filter_query: " << is_wildcard_no_filter_query;
auto intersect_fn = [&] (std::list<facet_count_t>::const_iterator facet_count_it) {
uint32_t count = 0;
if(is_wildcard_no_filter_query) {
count = facet_count.count;
if (is_wildcard_no_filter_query) {
count = facet_count_it->count;
} else {
auto ids = facet_index_map.at(facet_count.facet_value).seq_ids;
if(!ids) {
continue;
auto ids = facet_index_map.at(facet_count_it->facet_value).seq_ids;
if (!ids) {
return;
}
count = ids_t::intersect_count(ids, result_ids, result_ids_len);
}
if(count) {
found[facet_count.facet_value] = count;
if(found.size() == max_facets) {
if (count) {
found[facet_count_it->facet_value] = count;
}
};
if(sort_order.empty()) {
for (auto facet_count_it = counter_list.begin(); facet_count_it != counter_list.end();
++facet_count_it) {
//LOG(INFO) << "checking ids in facet_value " << facet_count.facet_value << " having total count "
// << facet_count.count << ", is_wildcard_no_filter_query: " << is_wildcard_no_filter_query;
intersect_fn(facet_count_it);
if (found.size() == max_facets) {
break;
}
}
} else {
if(sort_order == "asc") {
for(auto facet_index_map_it = facet_index_map.begin();
facet_index_map_it != facet_index_map.end(); ++facet_index_map_it) {
intersect_fn(facet_index_map_it->second.facet_count_it);
if (found.size() == max_facets) {
break;
}
}
} else if(sort_order == "desc") {
for(auto facet_index_map_it = facet_index_map.rbegin();
facet_index_map_it != facet_index_map.rend(); ++facet_index_map_it) {
intersect_fn(facet_index_map_it->second.facet_count_it);
if (found.size() == max_facets) {
break;
}
}
}
}
return found.size();
@ -209,12 +239,14 @@ size_t facet_index_t::get_facet_indexes(const std::string& field_name,
std::vector<uint32_t> id_list;
for(auto facet_index_map_it = facet_index_map.begin(); facet_index_map_it != facet_index_map.end(); ++facet_index_map_it) {
auto ids = facet_index_map_it->seq_ids;
//auto ids = facet_index_map_it->seq_ids;
auto ids = facet_index_map_it->second.seq_ids;
ids_t::uncompress(ids, id_list);
// emplacing seq_id => next_facet_id
for(const auto& id : id_list) {
seqid_countIndexes[id].emplace_back(facet_index_map_it->facet_id);
//seqid_countIndexes[id].emplace_back(facet_index_map_it->facet_id);
seqid_countIndexes[id].emplace_back(facet_index_map_it->second.facet_id);
}
id_list.clear();
@ -253,7 +285,7 @@ void facet_index_t::handle_index_change(const std::string& field_name, size_t to
// drop the value index for this field
auto& fvalue_seq_ids = facet_index.fvalue_seq_ids;
for(auto it = fvalue_seq_ids.begin(); it != fvalue_seq_ids.end(); ++it) {
ids_t::destroy_list(it.value().seq_ids);
ids_t::destroy_list(it->second.seq_ids);
}
fvalue_seq_ids.clear();
facet_index.counts.clear();

View File

@ -770,7 +770,7 @@ void Index::index_field_in_memory(const field& afield, std::vector<index_record>
}
}
facet_index_v4->insert(afield.name, afield.is_string(), fvalue_to_seq_ids, seq_id_to_fvalues);
facet_index_v4->insert(afield.name, fvalue_to_seq_ids, seq_id_to_fvalues);
auto tree_it = search_index.find(afield.faceted_name());
if(tree_it == search_index.end()) {
@ -1247,7 +1247,7 @@ void Index::do_facets(std::vector<facet> & facets, facet_query_t & facet_query,
const size_t group_limit, const std::vector<std::string>& group_by_fields,
const uint32_t* result_ids, size_t results_size,
int max_facet_count, bool is_wildcard_query, bool no_filters_provided,
facet_index_type_t facet_index_type) const {
facet_index_type_t facet_index_type, const facet_sort_by& facet_sort_params) const {
if(results_size == 0) {
return ;
@ -1284,10 +1284,21 @@ void Index::do_facets(std::vector<facet> & facets, facet_query_t & facet_query,
(results_size > 1000 && num_facet_values < 250) ||
(results_size > 1000 && results_size * 2 > total_docs));
if(!facet_sort_params.param.empty() && facet_sort_params.param == "alpha") {
std::map<std::string, uint32_t> facet_results;
facet_index_v4->intersect(facet_field.name, result_ids, results_size, max_facet_count,
facet_results, is_wildcard_no_filter_query, facet_sort_params.order);
for(const auto& kv : facet_results) {
facet_count_t& facet_count = a_facet.result_map[kv.first];
facet_count.count = kv.second;
}
a_facet.is_intersected = true;
}
#ifdef TEST_BUILD
if(facet_index_type == VALUE) {
else if(facet_index_type == VALUE) {
#else
if(facet_value_index_exists && use_value_index) {
else if(facet_value_index_exists && use_value_index) {
#endif
// LOG(INFO) << "Using intersection to find facets";
a_facet.is_intersected = true;
@ -1742,7 +1753,7 @@ Option<bool> Index::do_reference_filtering_with_lock(filter_node_t* const filter
}
Option<bool> Index::run_search(search_args* search_params, const std::string& collection_name,
facet_index_type_t facet_index_type) {
facet_index_type_t facet_index_type, const facet_sort_by& facet_sort_params) {
return search(search_params->field_query_tokens,
search_params->search_fields,
search_params->match_type,
@ -1778,7 +1789,8 @@ Option<bool> Index::run_search(search_args* search_params, const std::string& co
search_params->facet_sample_percent,
search_params->facet_sample_threshold,
collection_name,
facet_index_type);
facet_index_type,
facet_sort_params);
}
void Index::collate_included_ids(const std::vector<token_t>& q_included_tokens,
@ -2227,7 +2239,8 @@ Option<bool> Index::search(std::vector<query_tokens_t>& field_query_tokens, cons
const bool filter_curated_hits, const enable_t split_join_tokens,
const vector_query_t& vector_query,
size_t facet_sample_percent, size_t facet_sample_threshold,
const std::string& collection_name, facet_index_type_t facet_index_type) const {
const std::string& collection_name, facet_index_type_t facet_index_type,
const facet_sort_by& facet_sort_params) const {
std::shared_lock lock(mutex);
auto filter_result_iterator = new filter_result_iterator_t(collection_name, this, filter_tree_root);
@ -2848,7 +2861,7 @@ Option<bool> Index::search(std::vector<query_tokens_t>& field_query_tokens, cons
batch_result_ids, batch_res_len, &facet_infos, max_facet_values,
is_wildcard_query, no_filters_provided, estimate_facets, facet_sample_percent,
&parent_search_begin, &parent_search_stop_ms, &parent_search_cutoff,
&num_processed, &m_process, &cv_process, facet_index_type]() {
&num_processed, &m_process, &cv_process, facet_index_type, facet_sort_params]() {
search_begin_us = parent_search_begin;
search_stop_us = parent_search_stop_ms;
search_cutoff = parent_search_cutoff;
@ -2859,7 +2872,7 @@ Option<bool> Index::search(std::vector<query_tokens_t>& field_query_tokens, cons
facet_infos, group_limit, group_by_fields,
batch_result_ids, batch_res_len, max_facet_values,
is_wildcard_query, no_filters_provided,
facet_index_type);
facet_index_type, facet_sort_params);
std::unique_lock<std::mutex> lock(m_process);
num_processed++;
parent_search_cutoff = parent_search_cutoff || search_cutoff;
@ -2941,7 +2954,7 @@ Option<bool> Index::search(std::vector<query_tokens_t>& field_query_tokens, cons
do_facets(facets, facet_query, estimate_facets, facet_sample_percent,
facet_infos, group_limit, group_by_fields, &included_ids_vec[0],
included_ids_vec.size(), max_facet_values, is_wildcard_query, no_filters_provided,
facet_index_type);
facet_index_type, facet_sort_params);
all_result_ids_len += curated_topster->size;

View File

@ -1992,4 +1992,122 @@ TEST_F(CollectionFacetingTest, FacetSortByOtherFieldVal) {
ASSERT_EQ("Toyota", results["facet_counts"][0]["counts"][1]["value"]);
ASSERT_EQ("Tata", results["facet_counts"][0]["counts"][2]["value"]);
ASSERT_EQ("Maruti", results["facet_counts"][0]["counts"][3]["value"]);
}
TEST_F(CollectionFacetingTest, FacetSortByAlpha) {
nlohmann::json schema = R"({
"name": "coll1",
"fields": [
{"name": "phone", "type": "string", "optional": false, "facet": true },
{"name": "brand", "type": "string", "optional": false, "facet": true }
]
})"_json;
auto op = collectionManager.create_collection(schema);
ASSERT_TRUE(op.ok());
Collection *coll1 = op.get();
nlohmann::json doc;
doc["phone"] = "Oneplus 11R";
doc["brand"] = "Oneplus";
auto add_op = coll1->add(doc.dump(), CREATE);
ASSERT_TRUE(add_op.ok());
doc["phone"] = "Fusion Plus";
doc["brand"] = "Moto";
add_op = coll1->add(doc.dump(), CREATE);
ASSERT_TRUE(add_op.ok());
doc["phone"] = "S22 Ultra";
doc["brand"] = "Samsung";
add_op = coll1->add(doc.dump(), CREATE);
ASSERT_TRUE(add_op.ok());
doc["phone"] = "GT Master";
doc["brand"] = "Realme";
add_op = coll1->add(doc.dump(), CREATE);
ASSERT_TRUE(add_op.ok());
doc["phone"] = "T2";
doc["brand"] = "Vivo";
add_op = coll1->add(doc.dump(), CREATE);
ASSERT_TRUE(add_op.ok());
doc["phone"] = "Mi 6";
doc["brand"] = "Xiaomi";
add_op = coll1->add(doc.dump(), CREATE);
ASSERT_TRUE(add_op.ok());
doc["phone"] = "Z6 Lite";
doc["brand"] = "Iqoo";
add_op = coll1->add(doc.dump(), CREATE);
ASSERT_TRUE(add_op.ok());
//sort facets by phone in asc order
facet_sort_by facet_sort_param{"alpha", "asc"};
auto search_op = coll1->search("*", {}, "", {"phone"},
{}, {2}, 10, 1, FREQUENCY, {true},
1, spp::sparse_hash_set<std::string>(),
spp::sparse_hash_set<std::string>(), 10, "",
30, 4, "",
Index::TYPO_TOKENS_THRESHOLD, "", "", {},
3, "<mark>", "</mark>", {},
UINT32_MAX, true, false, true,
"", false, 6000 * 1000, 4, 7,
fallback, 4, {off}, INT16_MAX, INT16_MAX,
2, 2, false, "",
true, 0, max_score, 100,
0, 0, HASH, 30000,
2, "", {}, facet_sort_param);
if (!search_op.ok()) {
LOG(ERROR) << search_op.error();
FAIL();
}
auto results = search_op.get();
ASSERT_EQ(1, results["facet_counts"].size());
ASSERT_EQ(7, results["facet_counts"][0]["counts"].size());
ASSERT_EQ("Fusion Plus", results["facet_counts"][0]["counts"][0]["value"]);
ASSERT_EQ("GT Master", results["facet_counts"][0]["counts"][1]["value"]);
ASSERT_EQ("Mi 6", results["facet_counts"][0]["counts"][2]["value"]);
ASSERT_EQ("Oneplus 11R", results["facet_counts"][0]["counts"][3]["value"]);
ASSERT_EQ("S22 Ultra", results["facet_counts"][0]["counts"][4]["value"]);
ASSERT_EQ("T2", results["facet_counts"][0]["counts"][5]["value"]);
ASSERT_EQ("Z6 Lite", results["facet_counts"][0]["counts"][6]["value"]);
//sort facets by brand in desc order
facet_sort_param.param = "alpha";
facet_sort_param.order = "desc";
search_op = coll1->search("*", {}, "", {"brand"},
{}, {2}, 10, 1, FREQUENCY, {true},
1, spp::sparse_hash_set<std::string>(),
spp::sparse_hash_set<std::string>(), 10, "",
30, 4, "",
Index::TYPO_TOKENS_THRESHOLD, "", "", {},
3, "<mark>", "</mark>", {},
UINT32_MAX, true, false, true,
"", false, 6000 * 1000, 4, 7,
fallback, 4, {off}, INT16_MAX, INT16_MAX,
2, 2, false, "",
true, 0, max_score, 100,
0, 0, HASH, 30000,
2, "", {}, facet_sort_param);
if (!search_op.ok()) {
LOG(ERROR) << search_op.error();
FAIL();
}
results = search_op.get();
ASSERT_EQ(1, results["facet_counts"].size());
ASSERT_EQ(7, results["facet_counts"][0]["counts"].size());
ASSERT_EQ("Xiaomi", results["facet_counts"][0]["counts"][0]["value"]);
ASSERT_EQ("Vivo", results["facet_counts"][0]["counts"][1]["value"]);
ASSERT_EQ("Samsung", results["facet_counts"][0]["counts"][2]["value"]);
ASSERT_EQ("Realme", results["facet_counts"][0]["counts"][3]["value"]);
ASSERT_EQ("Oneplus", results["facet_counts"][0]["counts"][4]["value"]);
ASSERT_EQ("Moto", results["facet_counts"][0]["counts"][5]["value"]);
ASSERT_EQ("Iqoo", results["facet_counts"][0]["counts"][6]["value"]);
}