diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 67b557067e6f8a8d2b5ff22de9cc06aca0d09e62..b42548d16d5800a576d7cff8c48a62f9d4395bcc 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -356,7 +356,6 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; %type <sentence> grant_sentence revoke_sentence %type <sentence> set_config_sentence get_config_sentence balance_sentence -%type <sentence> process_control_sentence return_sentence %type <sentence> sentence %type <seq_sentences> seq_sentences %type <explain_sentence> explain_sentence @@ -371,7 +370,7 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; %left KW_OR KW_XOR %left KW_AND %right KW_NOT -%left EQ NE LT LE GT GE REG KW_IN KW_NOT_IN KW_CONTAINS KW_NOT_CONTAINS KW_STARTS_WITH KW_ENDS_WITH KW_NOT_STARTS_WITH KW_NOT_ENDS_WITH +%left EQ NE LT LE GT GE REG KW_IN KW_NOT_IN KW_CONTAINS KW_NOT_CONTAINS KW_STARTS_WITH KW_ENDS_WITH KW_NOT_STARTS_WITH KW_NOT_ENDS_WITH KW_IS_NULL KW_IS_NOT_NULL %left PLUS MINUS %left STAR DIV MOD %right NOT @@ -613,6 +612,12 @@ expression | expression KW_NOT_ENDS_WITH expression { $$ = new RelationalExpression(Expression::Kind::kNotEndsWith, $1, $3); } + | expression KW_IS_NULL { + $$ = new UnaryExpression(Expression::Kind::kIsNull, $1); + } + | expression KW_IS_NOT_NULL { + $$ = new UnaryExpression(Expression::Kind::kIsNotNull, $1); + } | expression EQ expression { $$ = new RelationalExpression(Expression::Kind::kRelEQ, $1, $3); } @@ -3152,22 +3157,12 @@ maintain_sentence | sign_out_text_search_service_sentence { $$ = $1; } ; -return_sentence - : KW_RETURN VARIABLE KW_IF VARIABLE KW_IS KW_NOT KW_NULL { - $$ = new ReturnSentence($2, $4); - } - -process_control_sentence - : return_sentence { $$ = $1; } - ; - sentence : maintain_sentence { $$ = $1; } | use_sentence { $$ = $1; } | set_sentence { $$ = $1; } | assignment_sentence { $$ = $1; } | mutate_sentence { $$ = $1; } - | process_control_sentence { $$ = $1; } ; seq_sentences diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex index 8ac8b03bac6afed3852004e960fbb63cf638c2f5..695a5d23fdbf13a48edfd7fe8fbf4cd3128f2a74 100644 --- a/src/parser/scanner.lex +++ b/src/parser/scanner.lex @@ -30,6 +30,8 @@ STARTS_WITH (STARTS{blanks}WITH) NOT_STARTS_WITH (NOT{blanks}STARTS{blanks}WITH) ENDS_WITH (ENDS{blanks}WITH) NOT_ENDS_WITH (NOT{blanks}ENDS{blanks}WITH) +IS_NULL (IS{blanks}NULL) +IS_NOT_NULL (IS{blanks}NOT{blanks}NULL) LABEL ([a-zA-Z][_a-zA-Z0-9]*) DEC ([0-9]) @@ -221,6 +223,8 @@ IP_OCTET ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]) "ENDS" { return TokenType::KW_ENDS;} {ENDS_WITH} { return TokenType::KW_ENDS_WITH;} {NOT_ENDS_WITH} { return TokenType::KW_NOT_ENDS_WITH;} +{IS_NULL} { return TokenType::KW_IS_NULL;} +{IS_NOT_NULL} { return TokenType::KW_IS_NOT_NULL;} "UNWIND" { return TokenType::KW_UNWIND;} "SKIP" { return TokenType::KW_SKIP;} "OPTIONAL" { return TokenType::KW_OPTIONAL;} diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index a0495c6230d55abfc2cb94acd7c3f95afd7ef46d..400c62898616eb2f51d0d00ac61b2fc6cac0cdbc 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -1910,14 +1910,6 @@ TEST(Parser, GroupBy) { } } -TEST(Parser, Return) { - { - std::string query = "RETURN $A IF $A IS NOT NULL"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } -} - TEST(Parser, ErrorMsg) { { std::string query = "CREATE SPACE " + std::string(4097, 'A'); diff --git a/src/visitor/DeduceTypeVisitor.cpp b/src/visitor/DeduceTypeVisitor.cpp index 1acf16b09d2a613a79fd26b23fad73433c953dc2..dc8ee02bd3637cc56152cd113cd6ed765b71df98 100644 --- a/src/visitor/DeduceTypeVisitor.cpp +++ b/src/visitor/DeduceTypeVisitor.cpp @@ -129,6 +129,14 @@ void DeduceTypeVisitor::visit(UnaryExpression *expr) { DETECT_UNARYEXPR_TYPE(!); break; } + case Expression::Kind::kIsNull: { + type_ = Value::Type::BOOL; + break; + } + case Expression::Kind::kIsNotNull: { + type_ = Value::Type::BOOL; + break; + } case Expression::Kind::kUnaryIncr: { auto detectVal = kConstantValues.at(type_) + 1; if (detectVal.isBadNull()) { diff --git a/src/visitor/ExtractPropExprVisitor.cpp b/src/visitor/ExtractPropExprVisitor.cpp index 70d09eb624b83fd4d74705f567ef98085551c54a..277903dcb39c95314d123436ea72fe64d510ef40 100644 --- a/src/visitor/ExtractPropExprVisitor.cpp +++ b/src/visitor/ExtractPropExprVisitor.cpp @@ -65,7 +65,9 @@ void ExtractPropExprVisitor::visit(UnaryExpression* expr) { switch (expr->kind()) { case Expression::Kind::kUnaryPlus: case Expression::Kind::kUnaryNegate: - case Expression::Kind::kUnaryNot: { + case Expression::Kind::kUnaryNot: + case Expression::Kind::kIsNull: + case Expression::Kind::kIsNotNull: { expr->operand()->accept(this); break; } diff --git a/src/visitor/RewriteInputPropVisitor.cpp b/src/visitor/RewriteInputPropVisitor.cpp index 73c93dcd9b6f8f71fff1723e038e5afdde2ab1e6..c41073cb15a0c44e2425474b636cc635db7e34f6 100644 --- a/src/visitor/RewriteInputPropVisitor.cpp +++ b/src/visitor/RewriteInputPropVisitor.cpp @@ -79,7 +79,9 @@ void RewriteInputPropVisitor::visit(UnaryExpression* expr) { switch (expr->kind()) { case Expression::Kind::kUnaryPlus: case Expression::Kind::kUnaryNegate: - case Expression::Kind::kUnaryNot: { + case Expression::Kind::kUnaryNot: + case Expression::Kind::kIsNull: + case Expression::Kind::kIsNotNull: { visitUnaryExpr(expr); break; } diff --git a/tests/tck/features/expression/UnaryExpr.feature b/tests/tck/features/expression/UnaryExpr.feature new file mode 100644 index 0000000000000000000000000000000000000000..021e157b7b3c0fe33dcbd2ae6a085ecf2dd2b560 --- /dev/null +++ b/tests/tck/features/expression/UnaryExpr.feature @@ -0,0 +1,78 @@ +# Copyright (c) 2020 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License, +# attached with Common Clause Condition 1.0, found in the LICENSES directory. +Feature: UnaryExpression + + Background: + Given a graph with space named "nba" + + Scenario: UnaryExpression basic + When executing query: + """ + YIELD [1 IS NULL, 1.2 IS NULL, true IS NULL, [1, 2] IS NULL, null IS NULL] AS isNull + """ + Then the result should be, in order: + | isNull | + | [false, false, false, false, true] | + When executing query: + """ + YIELD [1 IS NOT NULL, 1.2 IS NOT NULL, true IS NOT NULL, [1, 2] IS NOT NULL, null IS NOT NULL] AS isNotNull + """ + Then the result should be, in order: + | isNotNull | + | [true, true, true, true, false] | + When executing query: + """ + RETURN [1 IS NULL, 1.2 IS NULL, true IS NULL, [1, 2] IS NULL, null IS NULL] AS isNull + """ + Then the result should be, in order: + | isNull | + | [false, false, false, false, true] | + When executing query: + """ + RETURN [1 IS NOT NULL, 1.2 IS NOT NULL, true IS NOT NULL, [1, 2] IS NOT NULL, null IS NOT NULL] AS isNotNull + """ + Then the result should be, in order: + | isNotNull | + | [true, true, true, true, false] | + + Scenario: UnaryExpression in match clause + When executing query: + """ + MATCH (v:player) + WHERE v.name IS NULL AND v.age < 0 + RETURN v + """ + Then the result should be, in any order, with relax comparison: + | v | + | ("Null1":player{age:-1,name:NULL}) | + | ("Null2":player{age:-2,name:NULL}) | + | ("Null3":player{age:-3,name:NULL}) | + | ("Null4":player{age:-4,name:NULL}) | + When executing query: + """ + MATCH (v:player) + WHERE v.name IS NOT NULL AND v.age > 34 + RETURN v + """ + Then the result should be, in any order, with relax comparison: + | v | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | + | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | + | ("Ray Allen" :player{age: 43, name: "Ray Allen"}) | + | ("Boris Diaw" :player{age: 36, name: "Boris Diaw"}) | + | ("Paul Gasol" :player{age: 38, name: "Paul Gasol"}) | + | ("Amar'e Stoudemire" :player{age: 36, name: "Amar'e Stoudemire"}) | + | ("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"}) | + | ("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"}) | + | ("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"}) | + | ("Grant Hill" :player{age: 46, name: "Grant Hill"}) | + | ("David West" :player{age: 38, name: "David West"}) | + | ("Dirk Nowitzki" :player{age: 40, name: "Dirk Nowitzki"}) | + | ("Kobe Bryant" :player{age: 40, name: "Kobe Bryant"}) | + | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | + | ("Vince Carter" :player{age: 42, name: "Vince Carter"}) | + | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) |