diff --git a/src/core/search/lexer.lex b/src/core/search/lexer.lex index 4bbab7d6f..629a83109 100644 --- a/src/core/search/lexer.lex +++ b/src/core/search/lexer.lex @@ -66,7 +66,7 @@ term_char [_]|\w "AS" return Parser::make_AS (loc()); [0-9]+ return make_UINT32(matched_view(), loc()); -[+-]?([0-9]*[.])?[0-9]+ return make_DOUBLE(matched_view(), loc()); +[+-]?(([0-9]*[.])?[0-9]+|inf) return make_DOUBLE(matched_view(), loc()); {dq}{str_char}*{dq} return make_StringLit(matched_view(1, 1), loc()); diff --git a/src/core/search/parser.y b/src/core/search/parser.y index 79e1cd4f0..6acc3040b 100644 --- a/src/core/search/parser.y +++ b/src/core/search/parser.y @@ -74,6 +74,7 @@ using namespace std; %token DOUBLE "double" %token UINT32 "uint32" +%nterm generic_number %nterm final_query filter search_expr search_unary_expr search_or_expr search_and_expr %nterm field_cond field_cond_expr field_unary_expr field_or_expr field_and_expr tag_list @@ -123,13 +124,12 @@ search_unary_expr: | FIELD COLON field_cond { $$ = AstFieldNode(move($1), move($3)); } field_cond: - TERM { $$ = AstTermNode(move($1)); } - | UINT32 { $$ = AstTermNode(to_string($1)); } - | NOT_OP field_cond { $$ = AstNegateNode(move($2)); } - | LPAREN field_cond_expr RPAREN { $$ = move($2); } - | LBRACKET DOUBLE DOUBLE RBRACKET { $$ = AstRangeNode(move($2), move($3)); } - | LBRACKET UINT32 UINT32 RBRACKET { $$ = AstRangeNode(move($2), move($3)); } - | LCURLBR tag_list RCURLBR { $$ = move($2); } + TERM { $$ = AstTermNode(move($1)); } + | 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)); } + | LCURLBR tag_list RCURLBR { $$ = move($2); } field_cond_expr: field_unary_expr { $$ = move($1); } @@ -156,6 +156,10 @@ 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; } + %% void diff --git a/tests/dragonfly/requirements.txt b/tests/dragonfly/requirements.txt index b920c3808..4e85dbc85 100644 --- a/tests/dragonfly/requirements.txt +++ b/tests/dragonfly/requirements.txt @@ -19,3 +19,4 @@ numpy==1.24.3 pytest-json-report==1.5.0 psutil==5.9.5 boto3==1.28.55 +redis-om==0.2.1 diff --git a/tests/dragonfly/search_test.py b/tests/dragonfly/search_test.py index cb9b8ab08..115aff0b6 100644 --- a/tests/dragonfly/search_test.py +++ b/tests/dragonfly/search_test.py @@ -433,3 +433,68 @@ async def test_index_persistence(df_server): await i1.dropindex() await i2.dropindex() + + +@dfly_args({"proactor_threads": 4}) +def test_redis_om(df_server): + try: + import redis_om + except ModuleNotFoundError: + pytest.skip("Redis-om not installed") + + client = redis.Redis(port=df_server.port) + + class TestCar(redis_om.HashModel): + producer: str = redis_om.Field(index=True) + description: str = redis_om.Field(index=True, full_text_search=True) + speed: int = redis_om.Field(index=True, sortable=True) + + class Meta: + database = client + + def extract_producers(testset): + return sorted([car.producer for car in testset]) + + def make_car(producer, description, speed): + return TestCar(producer=producer, description=description, speed=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("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), + ] + + for car in CARS: + car.save() + + redis_om.Migrator().run() + + # Get all cars + assert extract_producers(TestCar.find().all()) == extract_producers(CARS) + + # Get all cars which Audi or Honda + assert extract_producers( + TestCar.find((TestCar.producer == "Peugeot") | (TestCar.producer == "Mini")) + ) == ["Mini", "Peugeot"] + + # Get only fast cars + assert extract_producers(TestCar.find(TestCar.speed >= 150).all()) == extract_producers( + [c for c in CARS if c.speed >= 150] + ) + + # Get all cars which are fast based on description + assert extract_producers(TestCar.find(TestCar.description % "fast")) == ["Audi", "BMW"] + + # 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"] + + for index in client.execute_command("FT._LIST"): + client.ft(index.decode()).dropindex()