From 37d3d6b25642bf48d2ec68403461de97abc1703c Mon Sep 17 00:00:00 2001 From: Harpreet Sangar Date: Mon, 9 Jan 2023 12:45:59 +0530 Subject: [PATCH] Support wildcard `facet_by`. (#850) * Support wildcard `facet_by`. * Only trim `*` from wildcard `facet_by`. * Add test for nested wildcard faceting. * Nested fields also match `company*`. --- src/collection.cpp | 13 +++++- test/collection_faceting_test.cpp | 67 ++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/collection.cpp b/src/collection.cpp index ae30fbf3..7853971f 100644 --- a/src/collection.cpp +++ b/src/collection.cpp @@ -4270,7 +4270,18 @@ Option Collection::parse_facet(const std::string& facet_field, std::vector a_facet.is_range_query = true; facets.emplace_back(std::move(a_facet)); - } else {//normal facet + } else if (facet_field.find('*') != std::string::npos) { // Wildcard + // Trim * from the end. + auto prefix = facet_field.substr(0, facet_field.size() - 1); + auto pair = search_schema.equal_prefix_range(prefix); + + // Collect the fields that match the prefix and are marked as facet. + for (auto field = pair.first; field != pair.second; field++) { + if (field->facet) { + facets.emplace_back(facet(field->name)); + } + } + } else {//normal facet facets.emplace_back(facet(facet_field)); } diff --git a/test/collection_faceting_test.cpp b/test/collection_faceting_test.cpp index c8674f82..023b81cf 100644 --- a/test/collection_faceting_test.cpp +++ b/test/collection_faceting_test.cpp @@ -946,7 +946,8 @@ TEST_F(CollectionFacetingTest, FacetByNestedIntField) { "enable_nested_fields": true, "fields": [ {"name": "details", "type": "object", "optional": false }, - {"name": "company.num_employees", "type": "int32", "optional": false, "facet": true } + {"name": "company.num_employees", "type": "int32", "optional": false, "facet": true }, + {"name": "companyRank", "type": "int32", "optional": false, "facet": true } ] })"_json; @@ -956,12 +957,14 @@ TEST_F(CollectionFacetingTest, FacetByNestedIntField) { auto doc1 = R"({ "details": {"count": 1000}, - "company": {"num_employees": 2000} + "company": {"num_employees": 2000}, + "companyRank": 100 })"_json; auto doc2 = R"({ "details": {"count": 2000}, - "company": {"num_employees": 2000} + "company": {"num_employees": 2000}, + "companyRank": 101 })"_json; ASSERT_TRUE(coll1->add(doc1.dump(), CREATE).ok()); @@ -979,6 +982,20 @@ TEST_F(CollectionFacetingTest, FacetByNestedIntField) { ASSERT_EQ(1, results["facet_counts"][0]["counts"].size()); ASSERT_EQ(2, results["facet_counts"][0]["counts"][0]["count"].get()); ASSERT_EQ("2000", results["facet_counts"][0]["counts"][0]["value"].get()); + + // Nested wildcard faceting + std::vector wildcard_facets; + coll1->parse_facet("company.*", wildcard_facets); + + ASSERT_EQ(1, wildcard_facets.size()); + ASSERT_EQ("company.num_employees", wildcard_facets[0].field_name); + + wildcard_facets.clear(); + coll1->parse_facet("company*", wildcard_facets); + + ASSERT_EQ(2, wildcard_facets.size()); + ASSERT_EQ("company.num_employees", wildcard_facets[0].field_name); + ASSERT_EQ("companyRank", wildcard_facets[1].field_name); } TEST_F(CollectionFacetingTest, FacetParseTest){ @@ -986,6 +1003,8 @@ TEST_F(CollectionFacetingTest, FacetParseTest){ field("score", field_types::INT32, true), field("grade", field_types::INT32, true), field("rank", field_types::INT32, true), + field("range", field_types::INT32, true), + field("scale", field_types::INT32, false), }; Collection* coll1 = collectionManager.create_collection("coll1", 1, fields).get(); @@ -1021,16 +1040,53 @@ TEST_F(CollectionFacetingTest, FacetParseTest){ ASSERT_STREQ("score", normal_facets[0].field_name.c_str()); ASSERT_STREQ("grade", normal_facets[1].field_name.c_str()); + std::vector wildcard_facet_fields { + "ran*", + "sc*", + }; + std::vector wildcard_facets; + for(const std::string & facet_field: wildcard_facet_fields) { + coll1->parse_facet(facet_field, wildcard_facets); + } + ASSERT_EQ(3, wildcard_facets.size()); + + ASSERT_EQ("rank", wildcard_facets[0].field_name); + ASSERT_EQ("range", wildcard_facets[1].field_name); + ASSERT_EQ("score", wildcard_facets[2].field_name); + + wildcard_facets.clear(); + coll1->parse_facet("*", wildcard_facets); + + // Last field is not a facet. + ASSERT_EQ(fields.size() - 1, wildcard_facets.size()); + + std::vector expected; + expected.resize(fields.size() - 1); + std::transform(fields.begin(), fields.end() - 1, expected.begin(), [] (const field& f) -> string { + return f.name; + }); + std::sort(expected.begin(), expected.end()); + + std::vector result; + result.resize(wildcard_facets.size()); + std::transform(wildcard_facets.begin(), wildcard_facets.end(), result.begin(), [] (const facet& f) -> string { + return f.field_name; + }); + std::sort(result.begin(), result.end()); + for (size_t i = 0; i < wildcard_facets.size(); i++) { + ASSERT_EQ(expected[i], result[i]); + } + std::vector mixed_facet_fields { "score", "grade(A:[80, 100], B:[60, 80], C:[40, 60])", - "rank" + "ra*", }; std::vector mixed_facets; for(const std::string & facet_field: mixed_facet_fields) { coll1->parse_facet(facet_field, mixed_facets); } - ASSERT_EQ(3, mixed_facets.size()); + ASSERT_EQ(4, mixed_facets.size()); ASSERT_STREQ("score", mixed_facets[0].field_name.c_str()); @@ -1039,6 +1095,7 @@ TEST_F(CollectionFacetingTest, FacetParseTest){ ASSERT_GT(mixed_facets[1].facet_range_map.size(), 0); ASSERT_STREQ("rank", mixed_facets[2].field_name.c_str()); + ASSERT_EQ("range", mixed_facets[3].field_name); } TEST_F(CollectionFacetingTest, RangeFacetTest) {