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:
parent
ac9f6170d6
commit
8792b8f931
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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*
|
||||
*/
|
||||
|
@ -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' ) ;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -82,4 +82,3 @@ struct hash<query::Symbol> {
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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 "
|
||||
|
59
tests/qa/tck_engine/tests/memgraph_V1/features/case.feature
Normal file
59
tests/qa/tck_engine/tests/memgraph_V1/features/case.feature
Normal 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'] |
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user