From cfcac97fdc9b9ea8959ac7e7c3f2157dfb7700f0 Mon Sep 17 00:00:00 2001 From: krunal Date: Thu, 10 Aug 2023 16:10:28 +0530 Subject: [PATCH] adding sort by alpha --- include/facet_index.h | 12 +-- include/index.h | 8 +- src/collection.cpp | 47 +++++++----- src/facet_index.cpp | 88 +++++++++++++++------- src/index.cpp | 33 ++++++--- test/collection_faceting_test.cpp | 118 ++++++++++++++++++++++++++++++ 6 files changed, 241 insertions(+), 65 deletions(-) diff --git a/include/facet_index.h b/include/facet_index.h index 50981801..cbca1da9 100644 --- a/include/facet_index.h +++ b/include/facet_index.h @@ -72,7 +72,7 @@ private: }; struct facet_doc_ids_list_t { - tsl::htrie_map fvalue_seq_ids; + std::map fvalue_seq_ids; std::list 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::Hash>& fvalue_to_seq_ids, + void insert(const std::string& field_name, std::unordered_map, facet_value_id_t::Hash>& fvalue_to_seq_ids, std::unordered_map>& 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& 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>& seqid_countIndexes); diff --git a/include/index.h b/include/index.h index 3c1976dd..f1c7712e 100644 --- a/include/index.h +++ b/include/index.h @@ -377,8 +377,7 @@ private: size_t group_limit, const std::vector& 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& tokens, filter_node_t*& filter_tree_root) const; @@ -633,7 +632,7 @@ public: // Public operations Option 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 search(std::vector& field_query_tokens, const std::vector& 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); diff --git a/src/collection.cpp b/src/collection.cpp index 09d91446..85543b3e 100644 --- a/src/collection.cpp +++ b/src/collection.cpp @@ -1631,7 +1631,7 @@ Option Collection::search(std::string raw_query, std::unique_ptr 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 Collection::search(std::string raw_query, } std::unordered_map ftoken_pos; - std::vector& ftokens = a_facet.hash_tokens[kv.first]; + std::vector& 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 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 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 Collection::parse_facet(const std::string& facet_field, std::vector } Option 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(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(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(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(400, error); + } - if(a_field.is_string()) { - std::string error = "Field for `facet_sort_by` should not be string"; - return Option(400, error); + if (a_field.is_string()) { + std::string error = "Field for `facet_sort_by` should not be string"; + return Option(400, error); + } } if ((facet_sort_params.order != "asc") && (facet_sort_params.order != "desc")) { diff --git a/src/facet_index.cpp b/src/facet_index.cpp index 7af3a4da..8c6fe69c 100644 --- a/src/facet_index.cpp +++ b/src/facet_index.cpp @@ -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::Hash>& fvalue_to_seq_ids, +void facet_index_t::insert(const std::string& field_name,std::unordered_map, facet_value_id_t::Hash>& fvalue_to_seq_ids, std::unordered_map>& 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& 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 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& 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::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 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(); diff --git a/src/index.cpp b/src/index.cpp index add2c363..bebb87e7 100644 --- a/src/index.cpp +++ b/src/index.cpp @@ -770,7 +770,7 @@ void Index::index_field_in_memory(const field& afield, std::vector } } - 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 & facets, facet_query_t & facet_query, const size_t group_limit, const std::vector& 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 & 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 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 Index::do_reference_filtering_with_lock(filter_node_t* const filter } Option 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 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& q_included_tokens, @@ -2227,7 +2239,8 @@ Option Index::search(std::vector& 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 Index::search(std::vector& 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 Index::search(std::vector& 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 lock(m_process); num_processed++; parent_search_cutoff = parent_search_cutoff || search_cutoff; @@ -2941,7 +2954,7 @@ Option Index::search(std::vector& 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; diff --git a/test/collection_faceting_test.cpp b/test/collection_faceting_test.cpp index 3e104c92..10ca9e42 100644 --- a/test/collection_faceting_test.cpp +++ b/test/collection_faceting_test.cpp @@ -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(), + spp::sparse_hash_set(), 10, "", + 30, 4, "", + Index::TYPO_TOKENS_THRESHOLD, "", "", {}, + 3, "", "", {}, + 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(), + spp::sparse_hash_set(), 10, "", + 30, 4, "", + Index::TYPO_TOKENS_THRESHOLD, "", "", {}, + 3, "", "", {}, + 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"]); } \ No newline at end of file