Implement CASE construct

Summary: Missing: documentation

Reviewers: buda, teon.banek

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D685
This commit is contained in:
Mislav Bradac 2017-08-20 13:53:50 +02:00
parent ac9f6170d6
commit 8792b8f931
19 changed files with 382 additions and 39 deletions

View File

@ -4,6 +4,8 @@
### Major Features and Improvements
* CASE construct (without aggregations)
### Bug Fixes and Other Changes
* Keywords appearing in header (named expressions) keep original case.

View File

@ -478,6 +478,10 @@ a dictionary and convert them to strings before running a query:
To use parameters with some other driver please consult appropriate
documentation.
#### CASE
TODO
### Differences
Although we try to implement openCypher query language as closely to the

View File

@ -447,6 +447,45 @@ class ListSlicingOperator : public Expression {
upper_bound_(upper_bound) {}
};
class IfOperator : public Expression {
friend class AstTreeStorage;
public:
DEFVISITABLE(TreeVisitor<TypedValue>);
bool Accept(HierarchicalTreeVisitor &visitor) override {
if (visitor.PreVisit(*this)) {
condition_->Accept(visitor) && then_expression_->Accept(visitor) &&
else_expression_->Accept(visitor);
}
return visitor.PostVisit(*this);
}
IfOperator *Clone(AstTreeStorage &storage) const override {
return storage.Create<IfOperator>(condition_->Clone(storage),
then_expression_->Clone(storage),
else_expression_->Clone(storage));
}
// None of the expressions should be nullptrs. If there is no else_expression
// you probably want to make it NULL PrimitiveLiteral.
Expression *condition_;
Expression *then_expression_;
Expression *else_expression_;
protected:
IfOperator(int uid, Expression *condition, Expression *then_expression,
Expression *else_expression)
: Expression(uid),
condition_(condition),
then_expression_(then_expression),
else_expression_(else_expression) {
debug_assert(
condition_ != nullptr && then_expression_ != nullptr &&
else_expression_ != nullptr,
"clause_, then_expression_ and else_expression_ can't be nullptr");
}
};
class NotOperator : public UnaryOperator {
friend class AstTreeStorage;

View File

@ -47,6 +47,7 @@ class GreaterEqualOperator;
class InListOperator;
class ListIndexingOperator;
class ListSlicingOperator;
class IfOperator;
class Delete;
class Where;
class SetProperty;
@ -64,11 +65,11 @@ using TreeCompositeVisitor = ::utils::CompositeVisitor<
MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator,
EqualOperator, LessOperator, GreaterOperator, LessEqualOperator,
GreaterEqualOperator, InListOperator, ListIndexingOperator,
ListSlicingOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator,
ListLiteral, MapLiteral, PropertyLookup, LabelsTest, EdgeTypeTest,
Aggregation, Function, All, Create, Match, Return, With, Pattern, NodeAtom,
EdgeAtom, BreadthFirstAtom, Delete, Where, SetProperty, SetProperties,
SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind>;
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator,
IsNullOperator, ListLiteral, MapLiteral, PropertyLookup, LabelsTest,
EdgeTypeTest, Aggregation, Function, All, Create, Match, Return, With,
Pattern, NodeAtom, EdgeAtom, BreadthFirstAtom, Delete, Where, SetProperty,
SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind>;
using TreeLeafVisitor =
::utils::LeafVisitor<Identifier, PrimitiveLiteral, CreateIndex>;
@ -89,11 +90,11 @@ using TreeVisitor = ::utils::Visitor<
MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator,
EqualOperator, LessOperator, GreaterOperator, LessEqualOperator,
GreaterEqualOperator, InListOperator, ListIndexingOperator,
ListSlicingOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator,
ListLiteral, MapLiteral, PropertyLookup, LabelsTest, EdgeTypeTest,
Aggregation, Function, All, Create, Match, Return, With, Pattern, NodeAtom,
EdgeAtom, BreadthFirstAtom, Delete, Where, SetProperty, SetProperties,
SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind, Identifier,
PrimitiveLiteral, CreateIndex>;
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator,
IsNullOperator, ListLiteral, MapLiteral, PropertyLookup, LabelsTest,
EdgeTypeTest, Aggregation, Function, All, Create, Match, Return, With,
Pattern, NodeAtom, EdgeAtom, BreadthFirstAtom, Delete, Where, SetProperty,
SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind,
Identifier, PrimitiveLiteral, CreateIndex>;
} // namespace query

View File

@ -797,6 +797,8 @@ antlrcpp::Any CypherMainVisitor::visitAtom(CypherParser::AtomContext *ctx) {
Where *where = ctx->filterExpression()->where()->accept(this);
return static_cast<Expression *>(
storage_.Create<All>(ident, list_expr, where));
} else if (ctx->caseExpression()) {
return static_cast<Expression *>(ctx->caseExpression()->accept(this));
}
// TODO: Implement this. We don't support comprehensions, filtering... at
// the moment.
@ -1032,6 +1034,36 @@ antlrcpp::Any CypherMainVisitor::visitPropertyExpression(
return dynamic_cast<PropertyLookup *>(expression);
}
antlrcpp::Any CypherMainVisitor::visitCaseExpression(
CypherParser::CaseExpressionContext *ctx) {
Expression *test_expression =
ctx->test ? ctx->test->accept(this).as<Expression *>() : nullptr;
auto alternatives = ctx->caseAlternatives();
// Reverse alternatives so that tree of IfOperators can be built bottom-up.
std::reverse(alternatives.begin(), alternatives.end());
Expression *else_expression =
ctx->else_expression
? ctx->else_expression->accept(this).as<Expression *>()
: storage_.Create<PrimitiveLiteral>(TypedValue::Null);
for (auto *alternative : alternatives) {
Expression *condition =
test_expression
? storage_.Create<EqualOperator>(
test_expression, alternative->when_expression->accept(this))
: alternative->when_expression->accept(this).as<Expression *>();
Expression *then_expression = alternative->then_expression->accept(this);
else_expression = storage_.Create<IfOperator>(condition, then_expression,
else_expression);
}
return else_expression;
}
antlrcpp::Any CypherMainVisitor::visitCaseAlternatives(
CypherParser::CaseAlternativesContext *) {
debug_fail("Should never be called. See documentation in hpp.");
return 0;
}
antlrcpp::Any CypherMainVisitor::visitWith(CypherParser::WithContext *ctx) {
auto *with = storage_.Create<With>();
in_with_ = true;

View File

@ -521,6 +521,19 @@ class CypherMainVisitor : public antlropencypher::CypherBaseVisitor {
antlrcpp::Any visitPropertyExpression(
CypherParser::PropertyExpressionContext *ctx) override;
/**
* @return IfOperator*
*/
antlrcpp::Any visitCaseExpression(
CypherParser::CaseExpressionContext *ctx) override;
/**
* Never call this. Ast generation for this production is done in
* @c visitCaseExpression.
*/
antlrcpp::Any visitCaseAlternatives(
CypherParser::CaseAlternativesContext *ctx) override;
/**
* @return With*
*/

View File

@ -14,6 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* When changing this grammar make sure to update constants in
* src/query/frontend/stripped_lexer_constants.hpp (kKeywords, kSpecialTokens
* and bitsets) and src/query/frontend/ast/named_antlr_tokens.hpp if needed.
*/
grammar Cypher;
cypher : SP? statement ( SP? ';' )? SP? EOF ;
@ -174,6 +181,7 @@ expression2b : atom ( SP? propertyLookup )* ;
atom : literal
| parameter
| caseExpression
| ( COUNT SP? '(' SP? '*' SP? ')' )
| listComprehension
| patternComprehension
@ -232,6 +240,10 @@ patternComprehension : '[' SP? ( variable SP? '=' SP? )? relationshipsPattern SP
propertyLookup : '.' SP? ( propertyKeyName ) ;
caseExpression : ( ( CASE ( SP? caseAlternatives )+ ) | ( CASE SP? test=expression ( SP? caseAlternatives )+ ) ) ( SP? ELSE SP? else_expression=expression )? SP? END ;
caseAlternatives : WHEN SP? when_expression=expression SP? THEN SP? then_expression=expression ;
variable : symbolicName ;
StringLiteral : ( '"' ( StringLiteral_0 | EscapedChar )* '"' )
@ -348,6 +360,11 @@ symbolicName : UnescapedSymbolicName
| CONTAINS
| IS
| CYPHERNULL
| CASE
| WHEN
| THEN
| ELSE
| END
| COUNT
| FILTER
| EXTRACT
@ -429,6 +446,16 @@ IS : ( 'I' | 'i' ) ( 'S' | 's' ) ;
CYPHERNULL : ( 'N' | 'n' ) ( 'U' | 'u' ) ( 'L' | 'l' ) ( 'L' | 'l' ) ;
CASE : ( 'C' | 'c' ) ( 'A' | 'a' ) ( 'S' | 's' ) ( 'E' | 'e' ) ;
ELSE : ( 'E' | 'e' ) ( 'L' | 'l' ) ( 'S' | 's' ) ( 'E' | 'e' ) ;
END : ( 'E' | 'e' ) ( 'N' | 'n' ) ( 'D' | 'd' ) ;
WHEN : ( 'W' | 'w' ) ( 'H' | 'h' ) ( 'E' | 'e' ) ( 'N' | 'n' ) ;
THEN : ( 'T' | 't' ) ( 'H' | 'h' ) ( 'E' | 'e' ) ( 'N' | 'n' ) ;
COUNT : ( 'C' | 'c' ) ( 'O' | 'o' ) ( 'U' | 'u' ) ( 'N' | 'n' ) ( 'T' | 't' ) ;
FILTER : ( 'F' | 'f' ) ( 'I' | 'i' ) ( 'L' | 'l' ) ( 'T' | 't' ) ( 'E' | 'e' ) ( 'R' | 'r' ) ;

View File

@ -254,6 +254,17 @@ bool SymbolGenerator::PreVisit(Aggregation &aggr) {
"Using aggregation functions inside aggregation functions is not "
"allowed");
}
if (scope_.num_if_operators) {
// Neo allows aggregations here and produces very interesting behaviors.
// To simplify implementation at this moment we decided to completely
// disallow aggregations inside of the CASE.
// However, in some cases aggregation makes perfect sense, for example:
// CASE count(n) WHEN 10 THEN "YES" ELSE "NO" END.
// TODO: Rethink of allowing aggregations in some parts of the CASE
// construct.
throw SemanticException(
"Using aggregation functions inside of CASE is not allowed");
}
// Create a virtual symbol for aggregation result.
// Currently, we only have aggregation operators which return numbers.
symbol_table_[aggr] =
@ -268,6 +279,16 @@ bool SymbolGenerator::PostVisit(Aggregation &) {
return true;
}
bool SymbolGenerator::PreVisit(IfOperator &) {
++scope_.num_if_operators;
return true;
}
bool SymbolGenerator::PostVisit(IfOperator &) {
--scope_.num_if_operators;
return true;
}
bool SymbolGenerator::PreVisit(All &all) {
all.list_expression_->Accept(*this);
VisitWithIdentifiers(*all.where_, {all.identifier_});
@ -412,7 +433,7 @@ bool SymbolGenerator::PreVisit(BreadthFirstAtom &bf_atom) {
return false;
}
bool SymbolGenerator::PostVisit(BreadthFirstAtom &bf_atom) {
bool SymbolGenerator::PostVisit(BreadthFirstAtom &) {
scope_.visiting_edge = nullptr;
return true;
}

View File

@ -44,6 +44,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
ReturnType Visit(PrimitiveLiteral &) override { return true; }
bool PreVisit(Aggregation &) override;
bool PostVisit(Aggregation &) override;
bool PreVisit(IfOperator &) override;
bool PostVisit(IfOperator &) override;
bool PreVisit(All &) override;
// Pattern and its subparts.
@ -94,6 +96,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
// Match. Identifiers created by naming vertices, edges and paths are *not*
// stored in here.
std::vector<Identifier *> identifiers_in_match;
// Number of nested IfOperators.
int num_if_operators{0};
};
bool HasSymbol(const std::string &name);

View File

@ -82,4 +82,3 @@ struct hash<query::Symbol> {
};
} // namespace std

View File

@ -79,14 +79,14 @@ class Trie {
const int kBitsetSize = 65536;
const trie::Trie kKeywords = {
"union", "all", "optional", "match", "unwind", "as",
"merge", "on", "create", "set", "detach", "delete",
"remove", "with", "distinct", "return", "order", "by",
"skip", "limit", "ascending", "asc", "descending", "desc",
"where", "or", "xor", "and", "not", "in",
"starts", "ends", "contains", "is", "null", "count",
"filter", "extract", "any", "none", "single", "true",
"false"};
"union", "all", "optional", "match", "unwind", "as",
"merge", "on", "create", "set", "detach", "delete",
"remove", "with", "distinct", "return", "order", "by",
"skip", "limit", "ascending", "asc", "descending", "desc",
"where", "or", "xor", "and", "not", "in",
"starts", "ends", "contains", "is", "null", "case",
"when", "then", "else", "end", "count", "filter",
"extract", "any", "none", "single", "true", "false"};
// Unicode codepoints that are allowed at the start of the unescaped name.
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(std::string(

View File

@ -123,6 +123,22 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
return op.expression2_->Accept(*this);
}
TypedValue Visit(IfOperator &if_operator) override {
auto condition = if_operator.condition_->Accept(*this);
if (condition.IsNull()) {
return if_operator.then_expression_->Accept(*this);
}
if (condition.type() != TypedValue::Type::Bool) {
// At the moment IfOperator is used only in CASE construct.
throw QueryRuntimeException(
"'CASE' expected boolean expression, but got {}", condition.type());
}
if (condition.Value<bool>()) {
return if_operator.then_expression_->Accept(*this);
}
return if_operator.else_expression_->Accept(*this);
}
TypedValue Visit(InListOperator &in_list) override {
auto literal = in_list.expression1_->Accept(*this);
auto _list = in_list.expression2_->Accept(*this);

View File

@ -352,6 +352,23 @@ class ReturnBodyContext : public HierarchicalTreeVisitor {
return false;
}
bool PreVisit(IfOperator &if_operator) override {
if_operator.condition_->Accept(*this);
bool has_aggr = has_aggregation_.back();
has_aggregation_.pop_back();
if_operator.then_expression_->Accept(*this);
has_aggr = has_aggr || has_aggregation_.back();
has_aggregation_.pop_back();
if_operator.else_expression_->Accept(*this);
has_aggr = has_aggr || has_aggregation_.back();
has_aggregation_.pop_back();
has_aggregation_.emplace_back(has_aggr);
// TODO: Once we allow aggregations here, insert appropriate stuff in
// group_by.
debug_assert(!has_aggr, "Currently aggregations in CASE are not allowed");
return false;
}
bool PostVisit(Function &function) override {
debug_assert(function.arguments_.size() <= has_aggregation_.size(),
"Expected has_aggregation_ flags as much as there are "

View File

@ -0,0 +1,59 @@
Feature: Case
Scenario: Simple CASE:
Given an empty graph
When executing query:
"""
UNWIND range(1, 3) as x RETURN CASE x WHEN 2 THEN "two" END
"""
Then the result should be:
| CASE x WHEN 2 THEN "two" END |
| null |
| 'two' |
| null |
Scenario: Simple CASE with ELSE:
Given an empty graph
When executing query:
"""
UNWIND range(1, 3) as x RETURN CASE x WHEN 2 THEN "two" ELSE "nottwo" END as z
"""
Then the result should be:
| z |
| 'nottwo' |
| 'two' |
| 'nottwo' |
Scenario: Generic CASE:
Given an empty graph
When executing query:
"""
UNWIND range(1, 3) as x RETURN CASE WHEN x > 1 THEN "greater" END as z
"""
Then the result should be:
| z |
| null |
| 'greater' |
| 'greater' |
Scenario: Generic CASE multiple matched whens:
Given an empty graph
When executing query:
"""
UNWIND range(1, 3) as x RETURN CASE WHEN x > 10 THEN 10 WHEN x > 1 THEN 1 WHEN x > 0 THEN 0 WHEN x > "mirko" THEN 1000 END as z
"""
Then the result should be:
| z |
| 0 |
| 1 |
| 1 |
Scenario: Simple CASE in collect:
Given an empty graph
When executing query:
"""
UNWIND range(1, 3) as x RETURN collect(CASE x WHEN 2 THEN "two" ELSE "nottwo" END) as z
"""
Then the result should be:
| z |
| ['nottwo', 'two', 'nottwo'] |

View File

@ -62,3 +62,11 @@ Feature: Memgraph only tests (queries in which we choose to be incompatible with
CREATE(a:DELete)
"""
Then an error should be raised
Scenario: Aggregation in CASE:
Given an empty graph
When executing query:
"""
MATCH (n) RETURN CASE count(n) WHEN 10 THEN 10 END
"""
Then an error should be raised

View File

@ -4,7 +4,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
@ -20,7 +20,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
@ -36,7 +36,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
@ -50,7 +50,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
@ -65,7 +65,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'E"}), (b{name: "AiMe"}), (c{name: "aime"})
CREATE(a{name: "ai'M'E"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
@ -82,7 +82,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
@ -98,7 +98,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
@ -112,7 +112,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
@ -127,7 +127,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
@ -143,7 +143,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
CREATE(a{name: "ai'M'e"}), (b{name: "AiMe"}), (c{name: "aime"})
"""
When executing query:
"""
@ -159,7 +159,7 @@ Feature: String operators
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
@ -169,12 +169,12 @@ Feature: String operators
"""
Then an error should be raised
Scenario: Contains test5
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
@ -183,4 +183,3 @@ Feature: String operators
return n.name
"""
Then an error should be raised

View File

@ -59,7 +59,7 @@ Feature: Unstable
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
@ -73,7 +73,7 @@ Feature: Unstable
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
@ -87,7 +87,7 @@ Feature: Unstable
Given an empty graph
And having executed
"""
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
CREATE(a{name: 1}), (b{name: 2}), (c{name: null})
"""
When executing query:
"""
@ -105,4 +105,3 @@ Feature: Unstable
Then the result should be:
| n |
| 2.718281828459045 |

View File

@ -541,6 +541,76 @@ TYPED_TEST(CypherMainVisitorTest, InWithListIndexing) {
EXPECT_TRUE(list_index);
}
TYPED_TEST(CypherMainVisitorTest, CaseGenericForm) {
TypeParam ast_generator(
"RETURN CASE WHEN n < 10 THEN 1 WHEN n > 10 THEN 2 END");
auto *query = ast_generator.query_;
auto *return_clause = dynamic_cast<Return *>(query->clauses_[0]);
auto *if_operator = dynamic_cast<IfOperator *>(
return_clause->body_.named_expressions[0]->expression_);
ASSERT_TRUE(if_operator);
auto *condition = dynamic_cast<LessOperator *>(if_operator->condition_);
ASSERT_TRUE(condition);
auto *then_expression =
dynamic_cast<PrimitiveLiteral *>(if_operator->then_expression_);
ASSERT_TRUE(then_expression);
ASSERT_EQ(then_expression->value_.Value<int64_t>(), 1);
auto *if_operator2 =
dynamic_cast<IfOperator *>(if_operator->else_expression_);
ASSERT_TRUE(if_operator2);
auto *condition2 = dynamic_cast<GreaterOperator *>(if_operator2->condition_);
ASSERT_TRUE(condition2);
auto *then_expression2 =
dynamic_cast<PrimitiveLiteral *>(if_operator2->then_expression_);
ASSERT_TRUE(then_expression2);
ASSERT_EQ(then_expression2->value_.Value<int64_t>(), 2);
auto *else_expression2 =
dynamic_cast<PrimitiveLiteral *>(if_operator2->else_expression_);
ASSERT_TRUE(else_expression2);
ASSERT_TRUE(else_expression2->value_.IsNull());
}
TYPED_TEST(CypherMainVisitorTest, CaseGenericFormElse) {
TypeParam ast_generator("RETURN CASE WHEN n < 10 THEN 1 ELSE 2 END");
auto *query = ast_generator.query_;
auto *return_clause = dynamic_cast<Return *>(query->clauses_[0]);
auto *if_operator = dynamic_cast<IfOperator *>(
return_clause->body_.named_expressions[0]->expression_);
auto *condition = dynamic_cast<LessOperator *>(if_operator->condition_);
ASSERT_TRUE(condition);
auto *then_expression =
dynamic_cast<PrimitiveLiteral *>(if_operator->then_expression_);
ASSERT_EQ(then_expression->value_.Value<int64_t>(), 1);
auto *else_expression =
dynamic_cast<PrimitiveLiteral *>(if_operator->else_expression_);
ASSERT_TRUE(else_expression);
ASSERT_EQ(else_expression->value_.Value<int64_t>(), 2);
}
TYPED_TEST(CypherMainVisitorTest, CaseSimpleForm) {
TypeParam ast_generator("RETURN CASE 5 WHEN 10 THEN 1 END");
auto *query = ast_generator.query_;
auto *return_clause = dynamic_cast<Return *>(query->clauses_[0]);
auto *if_operator = dynamic_cast<IfOperator *>(
return_clause->body_.named_expressions[0]->expression_);
auto *condition = dynamic_cast<EqualOperator *>(if_operator->condition_);
ASSERT_TRUE(condition);
auto *expr1 = dynamic_cast<PrimitiveLiteral *>(condition->expression1_);
ASSERT_TRUE(expr1);
ASSERT_EQ(expr1->value_.Value<int64_t>(), 5);
auto *expr2 = dynamic_cast<PrimitiveLiteral *>(condition->expression2_);
ASSERT_TRUE(expr2);
ASSERT_EQ(expr2->value_.Value<int64_t>(), 10);
auto *then_expression =
dynamic_cast<PrimitiveLiteral *>(if_operator->then_expression_);
ASSERT_EQ(then_expression->value_.Value<int64_t>(), 1);
auto *else_expression =
dynamic_cast<PrimitiveLiteral *>(if_operator->else_expression_);
ASSERT_TRUE(else_expression);
ASSERT_TRUE(else_expression->value_.IsNull());
}
TYPED_TEST(CypherMainVisitorTest, IsNull) {
TypeParam ast_generator("RETURN 2 iS NulL");
auto *query = ast_generator.query_;

View File

@ -459,6 +459,39 @@ TEST(ExpressionEvaluator, ListSlicingOperator) {
}
}
TEST(ExpressionEvaluator, IfOperator) {
AstTreeStorage storage;
NoContextExpressionEvaluator eval;
auto *then_expression = storage.Create<PrimitiveLiteral>(10);
auto *else_expression = storage.Create<PrimitiveLiteral>(20);
{
auto *condition_true =
storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(2),
storage.Create<PrimitiveLiteral>(2));
auto *op = storage.Create<IfOperator>(condition_true, then_expression,
else_expression);
auto value = op->Accept(eval.eval);
ASSERT_EQ(value.Value<int64_t>(), 10);
}
{
auto *condition_false =
storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(2),
storage.Create<PrimitiveLiteral>(3));
auto *op = storage.Create<IfOperator>(condition_false, then_expression,
else_expression);
auto value = op->Accept(eval.eval);
ASSERT_EQ(value.Value<int64_t>(), 20);
}
{
auto *condition_exception =
storage.Create<AdditionOperator>(storage.Create<PrimitiveLiteral>(2),
storage.Create<PrimitiveLiteral>(3));
auto *op = storage.Create<IfOperator>(condition_exception, then_expression,
else_expression);
ASSERT_THROW(op->Accept(eval.eval), QueryRuntimeException);
}
}
TEST(ExpressionEvaluator, NotOperator) {
AstTreeStorage storage;
NoContextExpressionEvaluator eval;