Skip to content

Commit

Permalink
chore(search): Add exclusive range option (#2055)
Browse files Browse the repository at this point in the history
Signed-off-by: Vladislav Oleshko <[email protected]>
  • Loading branch information
dranikpg authored Oct 23, 2023
1 parent 15f54be commit 67bb397
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 15 deletions.
4 changes: 3 additions & 1 deletion src/core/search/ast_expr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <absl/strings/numbers.h>

#include <algorithm>
#include <cmath>
#include <regex>

#include "base/logging.h"
Expand All @@ -18,7 +19,8 @@ namespace dfly::search {
AstTermNode::AstTermNode(string term) : term{term} {
}

AstRangeNode::AstRangeNode(double lo, double hi) : lo{lo}, hi{hi} {
AstRangeNode::AstRangeNode(double lo, bool lo_excl, double hi, bool hi_excl)
: lo{lo_excl ? nextafter(lo, hi) : lo}, hi{hi_excl ? nextafter(hi, lo) : hi} {
}

AstNegateNode::AstNegateNode(AstNode&& node) : node{make_unique<AstNode>(move(node))} {
Expand Down
2 changes: 1 addition & 1 deletion src/core/search/ast_expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ struct AstTermNode {

// Matches numeric range
struct AstRangeNode {
AstRangeNode(double lo, double hi);
AstRangeNode(double lo, bool lo_excl, double hi, bool hi_excl);

double lo, hi;
};
Expand Down
19 changes: 14 additions & 5 deletions src/core/search/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ using namespace std;
%token <double> DOUBLE "double"
%token <uint32_t> UINT32 "uint32"
%nterm <double> generic_number
%nterm <AstExpr> final_query filter search_expr search_unary_expr search_or_expr search_and_expr
%nterm <bool> opt_lparen
%nterm <AstExpr> final_query filter search_expr search_unary_expr search_or_expr search_and_expr numeric_filter_expr
%nterm <AstExpr> field_cond field_cond_expr field_unary_expr field_or_expr field_and_expr tag_list

%nterm <AstKnnNode> knn_query
Expand Down Expand Up @@ -128,9 +129,20 @@ field_cond:
| UINT32 { $$ = AstTermNode(to_string($1)); }
| NOT_OP field_cond { $$ = AstNegateNode(move($2)); }
| LPAREN field_cond_expr RPAREN { $$ = move($2); }
| LBRACKET generic_number generic_number RBRACKET { $$ = AstRangeNode(move($2), move($3)); }
| LBRACKET numeric_filter_expr RBRACKET { $$ = move($2); }
| LCURLBR tag_list RCURLBR { $$ = move($2); }

numeric_filter_expr:
opt_lparen generic_number opt_lparen generic_number { $$ = AstRangeNode($2, $1, $4, $3); }

generic_number:
DOUBLE { $$ = $1; }
| UINT32 { $$ = $1; }

opt_lparen:
/* empty */ { $$ = false; }
| LPAREN { $$ = true; }

field_cond_expr:
field_unary_expr { $$ = move($1); }
| field_and_expr { $$ = move($1); }
Expand All @@ -156,9 +168,6 @@ tag_list:
| tag_list OR_OP TERM { $$ = AstTagsNode(move($1), move($3)); }
| tag_list OR_OP DOUBLE { $$ = AstTagsNode(move($1), to_string($3)); }

generic_number:
DOUBLE { $$ = $1; }
| UINT32 { $$ = $1; }

%%

Expand Down
23 changes: 18 additions & 5 deletions src/core/search/search_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,27 @@ TEST_F(SearchTest, MatchRange) {

TEST_F(SearchTest, MatchDoubleRange) {
PrepareSchema({{"f1", SchemaField::NUMERIC}});
PrepareQuery("@f1: [100.03 199.97]");

ExpectAll(Map{{"f1", "130"}}, Map{{"f1", "170"}}, Map{{"f1", "100.03"}}, Map{{"f1", "199.97"}});
{
PrepareQuery("@f1: [100.03 199.97]");

ExpectNone(Map{{"f1", "0"}}, Map{{"f1", "200"}}, Map{{"f1", "100.02999"}},
Map{{"f1", "199.9700001"}});
ExpectAll(Map{{"f1", "130"}}, Map{{"f1", "170"}}, Map{{"f1", "100.03"}}, Map{{"f1", "199.97"}});

EXPECT_TRUE(Check()) << GetError();
ExpectNone(Map{{"f1", "0"}}, Map{{"f1", "200"}}, Map{{"f1", "100.02999"}},
Map{{"f1", "199.9700001"}});

EXPECT_TRUE(Check()) << GetError();
}

{
PrepareQuery("@f1: [(100 (199.9]");

ExpectAll(Map{{"f1", "150"}}, Map{{"f1", "100.00001"}}, Map{{"f1", "199.8999999"}});

ExpectNone(Map{{"f1", "50"}}, Map{{"f1", "100"}}, Map{{"f1", "199.9"}}, Map{{"f1", "200"}});

EXPECT_TRUE(Check()) << GetError();
}
}

TEST_F(SearchTest, MatchStar) {
Expand Down
20 changes: 17 additions & 3 deletions tests/dragonfly/search_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,10 +461,11 @@ def make_car(producer, description, speed):
CARS = [
make_car("BMW", "Very fast and elegant", 200),
make_car("Audi", "Fast & stylish", 170),
make_car("Mercedes", "High class for high prices", 150),
make_car("Mercedes", "High class but expensive!", 150),
make_car("Honda", "Good allrounder with flashy looks", 120),
make_car("Peugeot", "Good allrounder for the whole family", 100),
make_car("Mini", "Fashinable cooper for the big city", 80),
make_car("John Deere", "It's not a car, it's a tractor in fact!", 50),
]

for car in CARS:
Expand All @@ -475,7 +476,7 @@ def make_car(producer, description, speed):
# Get all cars
assert extract_producers(TestCar.find().all()) == extract_producers(CARS)

# Get all cars which Audi or Honda
# Get all cars of a specific producer
assert extract_producers(
TestCar.find((TestCar.producer == "Peugeot") | (TestCar.producer == "Mini"))
) == ["Mini", "Peugeot"]
Expand All @@ -485,16 +486,29 @@ def make_car(producer, description, speed):
[c for c in CARS if c.speed >= 150]
)

# Get only slow cars
assert extract_producers(TestCar.find(TestCar.speed < 100).all()) == extract_producers(
[c for c in CARS if c.speed < 100]
)

# Get all cars which are fast based on description
assert extract_producers(TestCar.find(TestCar.description % "fast")) == ["Audi", "BMW"]

# Get all cars which are not marked as extensive by descriptions
assert extract_producers(
TestCar.find(~(TestCar.description % "expensive")).all()
) == extract_producers([c for c in CARS if c.producer != "Mercedes"])

# Get a fast allrounder
assert extract_producers(
TestCar.find((TestCar.speed >= 110) & (TestCar.description % "allrounder"))
) == ["Honda"]

# What's the slowest car
assert extract_producers([TestCar.find().sort_by("speed").first()]) == ["Mini"]
assert extract_producers([TestCar.find().sort_by("speed").first()]) == ["John Deere"]

# What's the fastest car
assert extract_producers([TestCar.find().sort_by("-speed").first()]) == ["BMW"]

for index in client.execute_command("FT._LIST"):
client.ft(index.decode()).dropindex()

0 comments on commit 67bb397

Please sign in to comment.