mirror of
https://github.com/typesense/typesense.git
synced 2025-05-23 07:09:44 +08:00
Merge branch 'v0.26-facets' into v0.26-facets
This commit is contained in:
commit
3b8b80e23e
@ -5289,6 +5289,7 @@ bool Collection::get_enable_nested_fields() {
|
||||
Option<bool> Collection::parse_facet(const std::string& facet_field, std::vector<facet>& facets) const {
|
||||
const std::regex base_pattern(".+\\(.*\\)");
|
||||
const std::regex range_pattern("[[a-zA-Z]+:\\[([0-9]+)\\,\\s*([0-9]+)\\]");
|
||||
const std::string _alpha = "_alpha";
|
||||
|
||||
if ((facet_field.find(":") != std::string::npos)
|
||||
&& (facet_field.find("sort") == std::string::npos)) { //range based facet
|
||||
@ -5457,9 +5458,21 @@ Option<bool> Collection::parse_facet(const std::string& facet_field, std::vector
|
||||
return Option<bool>(404, error);
|
||||
}
|
||||
|
||||
if (facet_field.find("sort") != std::string::npos) { //sort params are supplied with facet
|
||||
pos = facet_field.find("sort_field");
|
||||
if(pos == std::string::npos) { //alpha sort
|
||||
if (facet_field.find("sort_by") != std::string::npos) { //sort params are supplied with facet
|
||||
std::vector<std::string> tokens;
|
||||
StringUtils::split(facet_field, tokens, ":");
|
||||
|
||||
if(tokens.size() != 3) {
|
||||
std::string error = "Invalid sort format.";
|
||||
return Option<bool>(400, error);
|
||||
}
|
||||
|
||||
//remove possible whitespaces
|
||||
for(auto i=0; i < 3; ++i) {
|
||||
StringUtils::trim(tokens[i]);
|
||||
}
|
||||
|
||||
if(tokens[1] == _alpha) {
|
||||
const field &a_field = search_schema.at(facet_field_copy);
|
||||
if (!a_field.is_string()) {
|
||||
std::string error = "Facet field should be string type to apply alpha sort.";
|
||||
@ -5467,9 +5480,7 @@ Option<bool> Collection::parse_facet(const std::string& facet_field, std::vector
|
||||
}
|
||||
sort_alpha = true;
|
||||
} else { //sort_field based sort
|
||||
auto sort_field_fixed_len = strlen("sort_field:");
|
||||
auto sort_field_len = facet_field.size() - pos - sort_field_fixed_len - 1;
|
||||
sort_field = facet_field.substr(pos + sort_field_fixed_len, sort_field_len);
|
||||
sort_field = tokens[1];
|
||||
|
||||
if (search_schema.count(sort_field) == 0 || !search_schema.at(sort_field).facet) {
|
||||
std::string error = "Could not find a facet field named `" + sort_field + "` in the schema.";
|
||||
@ -5483,11 +5494,17 @@ Option<bool> Collection::parse_facet(const std::string& facet_field, std::vector
|
||||
}
|
||||
}
|
||||
|
||||
if (facet_field.find("asc") != std::string::npos) {
|
||||
if (tokens[2].find("asc") != std::string::npos) {
|
||||
order = "asc";
|
||||
} else if (facet_field.find("desc") != std::string::npos) {
|
||||
} else if (tokens[2].find("desc") != std::string::npos) {
|
||||
order = "desc";
|
||||
} else {
|
||||
std::string error = "Invalid sort param.";
|
||||
return Option<bool>(400, error);
|
||||
}
|
||||
} else if (facet_field != facet_field_copy) {
|
||||
std::string error = "Invalid sort format.";
|
||||
return Option<bool>(400, error);
|
||||
}
|
||||
|
||||
facets.emplace_back(facet(facet_field_copy, {}, false, sort_alpha,
|
||||
|
@ -2023,7 +2023,7 @@ TEST_F(CollectionFacetingTest, FacetSortByAlpha) {
|
||||
ASSERT_TRUE(add_op.ok());
|
||||
|
||||
//sort facets by phone in asc order
|
||||
auto search_op = coll1->search("*", {}, "", {"phone(sort:asc)"},
|
||||
auto search_op = coll1->search("*", {}, "", {"phone(sort_by:_alpha:asc)"},
|
||||
{}, {2});
|
||||
|
||||
if (!search_op.ok()) {
|
||||
@ -2043,7 +2043,7 @@ TEST_F(CollectionFacetingTest, FacetSortByAlpha) {
|
||||
ASSERT_EQ("Z6 Lite", results["facet_counts"][0]["counts"][6]["value"]);
|
||||
|
||||
//sort facets by brand in desc order
|
||||
search_op = coll1->search("*", {}, "", {"brand(sort:desc)"},
|
||||
search_op = coll1->search("*", {}, "", {"brand(sort_by:_alpha:desc)"},
|
||||
{}, {2});
|
||||
|
||||
if (!search_op.ok()) {
|
||||
@ -2063,7 +2063,8 @@ TEST_F(CollectionFacetingTest, FacetSortByAlpha) {
|
||||
ASSERT_EQ("Iqoo", results["facet_counts"][0]["counts"][6]["value"]);
|
||||
|
||||
//sort facets by brand in desc order and phone by asc order
|
||||
search_op = coll1->search("*", {}, "", {"brand(sort:desc)", "phone(sort:asc)"},
|
||||
search_op = coll1->search("*", {}, "", {"brand(sort_by:_alpha:desc)",
|
||||
"phone(sort_by:_alpha:asc)"},
|
||||
{}, {2});
|
||||
|
||||
if (!search_op.ok()) {
|
||||
@ -2091,13 +2092,6 @@ TEST_F(CollectionFacetingTest, FacetSortByAlpha) {
|
||||
ASSERT_EQ("S22 Ultra", results["facet_counts"][1]["counts"][4]["value"]);
|
||||
ASSERT_EQ("T2", results["facet_counts"][1]["counts"][5]["value"]);
|
||||
ASSERT_EQ("Z6 Lite", results["facet_counts"][1]["counts"][6]["value"]);
|
||||
|
||||
//try sort on non string field
|
||||
search_op = coll1->search("*", {}, "", {"rating(sort:desc)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Facet field should be string type to apply alpha sort.", search_op.error());
|
||||
}
|
||||
|
||||
TEST_F(CollectionFacetingTest, FacetSortByOtherField) {
|
||||
@ -2170,7 +2164,7 @@ TEST_F(CollectionFacetingTest, FacetSortByOtherField) {
|
||||
|
||||
//search by calories in asc order
|
||||
auto search_op = coll1->search("*", {},"",
|
||||
{"receipe.name(sort:asc, sort_field:receipe.calories)"},
|
||||
{"receipe.name(sort_by:receipe.calories:asc)"},
|
||||
{}, {2});
|
||||
|
||||
if(!search_op.ok()) {
|
||||
@ -2189,7 +2183,7 @@ TEST_F(CollectionFacetingTest, FacetSortByOtherField) {
|
||||
|
||||
//search by calories in desc order
|
||||
search_op = coll1->search("*", {},"",
|
||||
{"receipe.name(sort:desc, sort_field:receipe.calories)"},
|
||||
{"receipe.name(sort_by:receipe.calories:desc)"},
|
||||
{}, {2});
|
||||
|
||||
if(!search_op.ok()) {
|
||||
@ -2205,13 +2199,6 @@ TEST_F(CollectionFacetingTest, FacetSortByOtherField) {
|
||||
ASSERT_EQ("butter chicken", results["facet_counts"][0]["counts"][2]["value"]);
|
||||
ASSERT_EQ("noodles", results["facet_counts"][0]["counts"][3]["value"]);
|
||||
ASSERT_EQ("schezwan rice", results["facet_counts"][0]["counts"][4]["value"]);
|
||||
|
||||
//try sort by stirng field
|
||||
search_op = coll1->search("*", {}, "", {"receipe.name(sort:desc, sort_field:receipe.origin)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Sort field should be non string type to apply sort.", search_op.error());
|
||||
}
|
||||
|
||||
TEST_F(CollectionFacetingTest, FacetSortByOtherFloatField) {
|
||||
@ -2284,7 +2271,7 @@ TEST_F(CollectionFacetingTest, FacetSortByOtherFloatField) {
|
||||
|
||||
//search by calories in asc order
|
||||
auto search_op = coll1->search("*", {},"",
|
||||
{"investment.name(sort:asc, sort_field:investment.interest_rate)"},
|
||||
{"investment.name(sort_by:investment.interest_rate:asc)"},
|
||||
{}, {2});
|
||||
|
||||
if(!search_op.ok()) {
|
||||
@ -2303,7 +2290,7 @@ TEST_F(CollectionFacetingTest, FacetSortByOtherFloatField) {
|
||||
|
||||
//search by calories in desc order
|
||||
search_op = coll1->search("*", {},"",
|
||||
{"investment.name(sort:desc, sort_field:investment.interest_rate)"},
|
||||
{"investment.name(sort_by:investment.interest_rate:desc)"},
|
||||
{}, {2});
|
||||
|
||||
if(!search_op.ok()) {
|
||||
@ -2319,4 +2306,89 @@ TEST_F(CollectionFacetingTest, FacetSortByOtherFloatField) {
|
||||
ASSERT_EQ("Bonds", results["facet_counts"][0]["counts"][2]["value"]);
|
||||
ASSERT_EQ("Term Deposits", results["facet_counts"][0]["counts"][3]["value"]);
|
||||
ASSERT_EQ("Gold", results["facet_counts"][0]["counts"][4]["value"]);
|
||||
}
|
||||
|
||||
TEST_F(CollectionFacetingTest, FacetSortValidation) {
|
||||
nlohmann::json schema = R"({
|
||||
"name": "coll1",
|
||||
"fields": [
|
||||
{"name": "phone", "type": "string", "optional": false, "facet": true },
|
||||
{"name": "brand", "type": "string", "optional": false, "facet": true },
|
||||
{"name": "rating", "type": "float", "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";
|
||||
doc["rating"] = 4.6;
|
||||
auto add_op = coll1->add(doc.dump(), CREATE);
|
||||
ASSERT_TRUE(add_op.ok());
|
||||
|
||||
doc["phone"] = "Fusion Plus";
|
||||
doc["brand"] = "Moto";
|
||||
doc["rating"] = 4.2;
|
||||
add_op = coll1->add(doc.dump(), CREATE);
|
||||
ASSERT_TRUE(add_op.ok());
|
||||
|
||||
doc["phone"] = "S22 Ultra";
|
||||
doc["brand"] = "Samsung";
|
||||
doc["rating"] = 4.1;
|
||||
add_op = coll1->add(doc.dump(), CREATE);
|
||||
ASSERT_TRUE(add_op.ok());
|
||||
|
||||
//try sort on non string field
|
||||
auto search_op = coll1->search("*", {}, "", {"rating(sort_by:_alpha:desc)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Facet field should be string type to apply alpha sort.", search_op.error());
|
||||
|
||||
//try sort by string field
|
||||
search_op = coll1->search("*", {}, "", {"phone(sort_by:brand:desc)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Sort field should be non string type to apply sort.", search_op.error());
|
||||
|
||||
//incorrect syntax
|
||||
search_op = coll1->search("*", {}, "", {"phone(sort_by:desc)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Invalid sort format.", search_op.error());
|
||||
|
||||
search_op = coll1->search("*", {}, "", {"phone(sort:_alpha:desc)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Invalid sort format.", search_op.error());
|
||||
|
||||
//invalid param
|
||||
search_op = coll1->search("*", {}, "", {"phone(sort_by:_alpha:foo)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Invalid sort param.", search_op.error());
|
||||
|
||||
//whitespace is allowed
|
||||
search_op = coll1->search("*", {}, "", {"phone( sort_by: _alpha : asc)"},
|
||||
{}, {2});
|
||||
|
||||
if (!search_op.ok()) {
|
||||
LOG(ERROR) << search_op.error();
|
||||
FAIL();
|
||||
}
|
||||
|
||||
auto results = search_op.get();
|
||||
ASSERT_EQ(1, results["facet_counts"].size());
|
||||
ASSERT_EQ(3, results["facet_counts"][0]["counts"].size());
|
||||
ASSERT_EQ("Fusion Plus", results["facet_counts"][0]["counts"][0]["value"]);
|
||||
ASSERT_EQ("Oneplus 11R", results["facet_counts"][0]["counts"][1]["value"]);
|
||||
ASSERT_EQ("S22 Ultra", results["facet_counts"][0]["counts"][2]["value"]);
|
||||
}
|
@ -1501,7 +1501,7 @@ TEST_F(CollectionOptimizedFacetingTest, FacetSortByAlpha) {
|
||||
ASSERT_TRUE(add_op.ok());
|
||||
|
||||
//sort facets by phone in asc order
|
||||
auto search_op = coll1->search("*", {}, "", {"phone(sort:asc)"},
|
||||
auto search_op = coll1->search("*", {}, "", {"phone(sort_by:_alpha:asc)"},
|
||||
{}, {2});
|
||||
|
||||
if (!search_op.ok()) {
|
||||
@ -1521,7 +1521,7 @@ TEST_F(CollectionOptimizedFacetingTest, FacetSortByAlpha) {
|
||||
ASSERT_EQ("Z6 Lite", results["facet_counts"][0]["counts"][6]["value"]);
|
||||
|
||||
//sort facets by brand in desc order
|
||||
search_op = coll1->search("*", {}, "", {"brand(sort:desc)"},
|
||||
search_op = coll1->search("*", {}, "", {"brand(sort_by:_alpha:desc)"},
|
||||
{}, {2});
|
||||
|
||||
if (!search_op.ok()) {
|
||||
@ -1541,7 +1541,8 @@ TEST_F(CollectionOptimizedFacetingTest, FacetSortByAlpha) {
|
||||
ASSERT_EQ("Iqoo", results["facet_counts"][0]["counts"][6]["value"]);
|
||||
|
||||
//sort facets by brand in desc order and phone by asc order
|
||||
search_op = coll1->search("*", {}, "", {"brand(sort:desc)", "phone(sort:asc)"},
|
||||
search_op = coll1->search("*", {}, "", {"brand(sort_by:_alpha:desc)",
|
||||
"phone(sort_by:_alpha:asc)"},
|
||||
{}, {2});
|
||||
|
||||
if (!search_op.ok()) {
|
||||
@ -1569,13 +1570,6 @@ TEST_F(CollectionOptimizedFacetingTest, FacetSortByAlpha) {
|
||||
ASSERT_EQ("S22 Ultra", results["facet_counts"][1]["counts"][4]["value"]);
|
||||
ASSERT_EQ("T2", results["facet_counts"][1]["counts"][5]["value"]);
|
||||
ASSERT_EQ("Z6 Lite", results["facet_counts"][1]["counts"][6]["value"]);
|
||||
|
||||
//try sort on non string field
|
||||
search_op = coll1->search("*", {}, "", {"rating(sort:desc)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Facet field should be string type to apply alpha sort.", search_op.error());
|
||||
}
|
||||
|
||||
TEST_F(CollectionOptimizedFacetingTest, FacetSortByOtherField) {
|
||||
@ -1648,7 +1642,7 @@ TEST_F(CollectionOptimizedFacetingTest, FacetSortByOtherField) {
|
||||
|
||||
//search by calories in asc order
|
||||
auto search_op = coll1->search("*", {},"",
|
||||
{"receipe.name(sort:asc, sort_field:receipe.calories)"},
|
||||
{"receipe.name(sort_by:receipe.calories:asc)"},
|
||||
{}, {2});
|
||||
|
||||
if(!search_op.ok()) {
|
||||
@ -1667,7 +1661,7 @@ TEST_F(CollectionOptimizedFacetingTest, FacetSortByOtherField) {
|
||||
|
||||
//search by calories in desc order
|
||||
search_op = coll1->search("*", {},"",
|
||||
{"receipe.name(sort:desc, sort_field:receipe.calories)"},
|
||||
{"receipe.name(sort_by:receipe.calories:desc)"},
|
||||
{}, {2});
|
||||
|
||||
if(!search_op.ok()) {
|
||||
@ -1683,13 +1677,6 @@ TEST_F(CollectionOptimizedFacetingTest, FacetSortByOtherField) {
|
||||
ASSERT_EQ("butter chicken", results["facet_counts"][0]["counts"][2]["value"]);
|
||||
ASSERT_EQ("noodles", results["facet_counts"][0]["counts"][3]["value"]);
|
||||
ASSERT_EQ("schezwan rice", results["facet_counts"][0]["counts"][4]["value"]);
|
||||
|
||||
//try sort by stirng field
|
||||
search_op = coll1->search("*", {}, "", {"receipe.name(sort:desc, sort_field:receipe.origin)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Sort field should be non string type to apply sort.", search_op.error());
|
||||
}
|
||||
|
||||
TEST_F(CollectionOptimizedFacetingTest, FacetSortByOtherFloatField) {
|
||||
@ -1762,7 +1749,7 @@ TEST_F(CollectionOptimizedFacetingTest, FacetSortByOtherFloatField) {
|
||||
|
||||
//search by calories in asc order
|
||||
auto search_op = coll1->search("*", {},"",
|
||||
{"investment.name(sort:asc, sort_field:investment.interest_rate)"},
|
||||
{"investment.name(sort_by:investment.interest_rate:asc)"},
|
||||
{}, {2});
|
||||
|
||||
if(!search_op.ok()) {
|
||||
@ -1781,7 +1768,7 @@ TEST_F(CollectionOptimizedFacetingTest, FacetSortByOtherFloatField) {
|
||||
|
||||
//search by calories in desc order
|
||||
search_op = coll1->search("*", {},"",
|
||||
{"investment.name(sort:desc, sort_field:investment.interest_rate)"},
|
||||
{"investment.name(sort_by:investment.interest_rate:desc)"},
|
||||
{}, {2});
|
||||
|
||||
if(!search_op.ok()) {
|
||||
@ -1797,4 +1784,90 @@ TEST_F(CollectionOptimizedFacetingTest, FacetSortByOtherFloatField) {
|
||||
ASSERT_EQ("Bonds", results["facet_counts"][0]["counts"][2]["value"]);
|
||||
ASSERT_EQ("Term Deposits", results["facet_counts"][0]["counts"][3]["value"]);
|
||||
ASSERT_EQ("Gold", results["facet_counts"][0]["counts"][4]["value"]);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CollectionOptimizedFacetingTest, FacetSortValidation) {
|
||||
nlohmann::json schema = R"({
|
||||
"name": "coll1",
|
||||
"fields": [
|
||||
{"name": "phone", "type": "string", "optional": false, "facet": true },
|
||||
{"name": "brand", "type": "string", "optional": false, "facet": true },
|
||||
{"name": "rating", "type": "float", "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";
|
||||
doc["rating"] = 4.6;
|
||||
auto add_op = coll1->add(doc.dump(), CREATE);
|
||||
ASSERT_TRUE(add_op.ok());
|
||||
|
||||
doc["phone"] = "Fusion Plus";
|
||||
doc["brand"] = "Moto";
|
||||
doc["rating"] = 4.2;
|
||||
add_op = coll1->add(doc.dump(), CREATE);
|
||||
ASSERT_TRUE(add_op.ok());
|
||||
|
||||
doc["phone"] = "S22 Ultra";
|
||||
doc["brand"] = "Samsung";
|
||||
doc["rating"] = 4.1;
|
||||
add_op = coll1->add(doc.dump(), CREATE);
|
||||
ASSERT_TRUE(add_op.ok());
|
||||
|
||||
//try sort on non string field
|
||||
auto search_op = coll1->search("*", {}, "", {"rating(sort_by:_alpha:desc)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Facet field should be string type to apply alpha sort.", search_op.error());
|
||||
|
||||
//try sort by string field
|
||||
search_op = coll1->search("*", {}, "", {"phone(sort_by:brand:desc)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Sort field should be non string type to apply sort.", search_op.error());
|
||||
|
||||
//incorrect syntax
|
||||
search_op = coll1->search("*", {}, "", {"phone(sort_by:desc)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Invalid sort format.", search_op.error());
|
||||
|
||||
search_op = coll1->search("*", {}, "", {"phone(sort:_alpha:desc)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Invalid sort format.", search_op.error());
|
||||
|
||||
//invalid param
|
||||
search_op = coll1->search("*", {}, "", {"phone(sort_by:_alpha:foo)"},
|
||||
{}, {2});
|
||||
|
||||
ASSERT_EQ(400, search_op.code());
|
||||
ASSERT_EQ("Invalid sort param.", search_op.error());
|
||||
|
||||
//whitespace is allowed
|
||||
search_op = coll1->search("*", {}, "", {"phone( sort_by: _alpha : asc)"},
|
||||
{}, {2});
|
||||
|
||||
if (!search_op.ok()) {
|
||||
LOG(ERROR) << search_op.error();
|
||||
FAIL();
|
||||
}
|
||||
|
||||
auto results = search_op.get();
|
||||
ASSERT_EQ(1, results["facet_counts"].size());
|
||||
ASSERT_EQ(3, results["facet_counts"][0]["counts"].size());
|
||||
ASSERT_EQ("Fusion Plus", results["facet_counts"][0]["counts"][0]["value"]);
|
||||
ASSERT_EQ("Oneplus 11R", results["facet_counts"][0]["counts"][1]["value"]);
|
||||
ASSERT_EQ("S22 Ultra", results["facet_counts"][0]["counts"][2]["value"]);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user