diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp index be5917a74..4d6c79aeb 100644 --- a/src/query/frontend/ast/ast.hpp +++ b/src/query/frontend/ast/ast.hpp @@ -347,6 +347,20 @@ class UnaryMinusOperator : public UnaryOperator { using UnaryOperator::UnaryOperator; }; +class IsNullOperator : public UnaryOperator { + friend class AstTreeStorage; + + public: + void Accept(TreeVisitorBase &visitor) override { + visitor.Visit(*this); + expression_->Accept(visitor); + visitor.PostVisit(*this); + } + + protected: + using UnaryOperator::UnaryOperator; +}; + class Literal : public Expression { friend class AstTreeStorage; diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp index 4ddc17583..7fecf156f 100644 --- a/src/query/frontend/ast/ast_visitor.hpp +++ b/src/query/frontend/ast/ast_visitor.hpp @@ -29,6 +29,7 @@ class DivisionOperator; class ModOperator; class UnaryPlusOperator; class UnaryMinusOperator; +class IsNullOperator; class NotEqualOperator; class EqualOperator; class LessOperator; @@ -48,8 +49,8 @@ using TreeVisitorBase = ::utils::Visitor< AdditionOperator, SubtractionOperator, MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator, EqualOperator, LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator, - UnaryPlusOperator, UnaryMinusOperator, Identifier, Literal, PropertyLookup, - Aggregation, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, - Delete, Where, SetProperty, SetProperties, SetLabels, RemoveProperty, - RemoveLabels>; + UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, Identifier, Literal, + PropertyLookup, Aggregation, Create, Match, Return, With, Pattern, NodeAtom, + EdgeAtom, Delete, Where, SetProperty, SetProperties, SetLabels, + RemoveProperty, RemoveLabels>; } diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index a900a1b73..13aae2112 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -496,7 +496,21 @@ antlrcpp::Any CypherMainVisitor::visitExpression3( // that child is expression2. Other operations are not implemented at the // moment. // TODO: implement this. - if (ctx->children.size() > 1u) { + // This is a hack. Unfortunately, grammar for expression3 contains a lot of + // different expressions. We should break that production in parts so that + // we can easily implement its visitor. + Expression *expression = ctx->expression2()[0]->accept(this); + if (ctx->children.size() - ctx->SP().size() == 3U && ctx->IS().size() == 1U && + ctx->CYPHERNULL().size() == 1U) { + return static_cast( + storage_.Create(expression)); + } + if (ctx->children.size() - ctx->SP().size() == 4U && ctx->IS().size() == 1U && + ctx->NOT().size() == 1U && ctx->CYPHERNULL().size() == 1U) { + return static_cast(storage_.Create( + storage_.Create(expression))); + } + if (ctx->children.size() > 1U) { throw NotYetImplemented(); } return static_cast(visitChildren(ctx)); diff --git a/src/query/frontend/interpret/interpret.hpp b/src/query/frontend/interpret/interpret.hpp index 84740369b..d0b5bee1d 100644 --- a/src/query/frontend/interpret/interpret.hpp +++ b/src/query/frontend/interpret/interpret.hpp @@ -105,6 +105,12 @@ class ExpressionEvaluator : public TreeVisitorBase { UNARY_OPERATOR_VISITOR(UnaryPlusOperator, +); UNARY_OPERATOR_VISITOR(UnaryMinusOperator, -); + void PostVisit(IsNullOperator &) override { + auto expression = PopBack(); + result_stack_.push_back( + TypedValue(expression.type() == TypedValue::Type::Null)); + } + #undef BINARY_OPERATOR_VISITOR #undef UNARY_OPERATOR_VISITOR diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp index 701907f74..b9632150d 100644 --- a/tests/unit/cypher_main_visitor.cpp +++ b/tests/unit/cypher_main_visitor.cpp @@ -277,6 +277,30 @@ TEST(CypherMainVisitorTest, ComparisonOperators) { #undef CHECK_COMPARISON +TEST(CypherMainVisitorTest, IsNull) { + AstGenerator ast_generator("RETURN 2 iS NulL"); + auto *query = ast_generator.query_; + auto *return_clause = dynamic_cast(query->clauses_[0]); + auto *is_type_operator = dynamic_cast( + return_clause->named_expressions_[0]->expression_); + auto *operand1 = dynamic_cast(is_type_operator->expression_); + ASSERT_TRUE(operand1); + ASSERT_EQ(operand1->value_.Value(), 2); +} + +TEST(CypherMainVisitorTest, IsNotNull) { + AstGenerator ast_generator("RETURN 2 iS nOT NulL"); + auto *query = ast_generator.query_; + auto *return_clause = dynamic_cast(query->clauses_[0]); + auto *not_operator = dynamic_cast( + return_clause->named_expressions_[0]->expression_); + auto *is_type_operator = + dynamic_cast(not_operator->expression_); + auto *operand1 = dynamic_cast(is_type_operator->expression_); + ASSERT_TRUE(operand1); + ASSERT_EQ(operand1->value_.Value(), 2); +} + TEST(CypherMainVisitorTest, NotOperator) { AstGenerator ast_generator("RETURN not true"); auto *query = ast_generator.query_; diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp index 4d9fee0b3..e344460ed 100644 --- a/tests/unit/query_expression_evaluator.cpp +++ b/tests/unit/query_expression_evaluator.cpp @@ -232,3 +232,15 @@ TEST(ExpressionEvaluator, UnaryMinusOperator) { op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), -5); } + +TEST(ExpressionEvaluator, IsNullOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(1)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); + op = + storage.Create(storage.Create(TypedValue::Null)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); +}