Add 'none' function implementation, its unit and TCK tests
Reviewers: mferencevic Reviewed By: mferencevic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2788
This commit is contained in:
parent
21cee1eaec
commit
8bfebfca9d
@ -988,8 +988,8 @@ cpp<#
|
||||
(:clone))
|
||||
|
||||
;; TODO: This is pretty much copy pasted from All. Consider merging Reduce,
|
||||
;; All, Any and Single into something like a higher-order function call which
|
||||
;; takes a list argument and a function which is applied on list elements.
|
||||
;; All, Any, None and Single into something like a higher-order function call
|
||||
;; which takes a list argument and a function which is applied on list elements.
|
||||
(lcp:define-class single (expression)
|
||||
((identifier "Identifier *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
@ -1029,8 +1029,8 @@ cpp<#
|
||||
(:clone))
|
||||
|
||||
;; TODO: This is pretty much copy pasted from All. Consider merging Reduce,
|
||||
;; All, Any and Single into something like a higher-order function call which
|
||||
;; takes a list argument and a function which is applied on list elements.
|
||||
;; All, Any, None and Single into something like a higher-order function call
|
||||
;; which takes a list argument and a function which is applied on list elements.
|
||||
(lcp:define-class any (expression)
|
||||
((identifier "Identifier *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
@ -1069,6 +1069,47 @@ cpp<#
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
;; TODO: This is pretty much copy pasted from All. Consider merging Reduce,
|
||||
;; All, Any, None and Single into something like a higher-order function call
|
||||
;; which takes a list argument and a function which is applied on list elements.
|
||||
(lcp:define-class none (expression)
|
||||
((identifier "Identifier *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Identifier"))
|
||||
(list-expression "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
(where "Where *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Where")))
|
||||
(:public
|
||||
#>cpp
|
||||
None() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
identifier_->Accept(visitor) && list_expression_->Accept(visitor) &&
|
||||
where_->Accept(visitor);
|
||||
}
|
||||
return visitor.PostVisit(*this);
|
||||
}
|
||||
cpp<#)
|
||||
(:protected
|
||||
#>cpp
|
||||
None(Identifier *identifier, Expression *list_expression, Where *where)
|
||||
: identifier_(identifier),
|
||||
list_expression_(list_expression),
|
||||
where_(where) {}
|
||||
cpp<#)
|
||||
(:private
|
||||
#>cpp
|
||||
friend class AstStorage;
|
||||
cpp<#)
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:define-class parameter-lookup (expression)
|
||||
((token-position :int32_t :initval -1 :scope :public
|
||||
:documentation "This field contains token position of *literal* used to create ParameterLookup object. If ParameterLookup object is not created from a literal leave this value at -1."))
|
||||
|
@ -20,6 +20,7 @@ class Extract;
|
||||
class All;
|
||||
class Single;
|
||||
class Any;
|
||||
class None;
|
||||
class ParameterLookup;
|
||||
class CallProcedure;
|
||||
class Create;
|
||||
@ -80,7 +81,7 @@ using TreeCompositeVisitor = ::utils::CompositeVisitor<
|
||||
GreaterEqualOperator, InListOperator, SubscriptOperator,
|
||||
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator,
|
||||
IsNullOperator, ListLiteral, MapLiteral, PropertyLookup, LabelsTest,
|
||||
Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any,
|
||||
Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, None,
|
||||
CallProcedure, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom,
|
||||
Delete, Where, SetProperty, SetProperties, SetLabels, RemoveProperty,
|
||||
RemoveLabels, Merge, Unwind, RegexMatch>;
|
||||
@ -108,7 +109,7 @@ class ExpressionVisitor
|
||||
SubscriptOperator, ListSlicingOperator, IfOperator, UnaryPlusOperator,
|
||||
UnaryMinusOperator, IsNullOperator, ListLiteral, MapLiteral,
|
||||
PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce,
|
||||
Extract, All, Single, Any, ParameterLookup, Identifier,
|
||||
Extract, All, Single, Any, None, ParameterLookup, Identifier,
|
||||
PrimitiveLiteral, RegexMatch> {};
|
||||
|
||||
template <class TResult>
|
||||
|
@ -1391,6 +1391,20 @@ antlrcpp::Any CypherMainVisitor::visitAtom(MemgraphCypher::AtomContext *ctx) {
|
||||
Where *where = ctx->filterExpression()->where()->accept(this);
|
||||
return static_cast<Expression *>(
|
||||
storage_->Create<Any>(ident, list_expr, where));
|
||||
} else if (ctx->NONE()) {
|
||||
auto *ident = storage_->Create<Identifier>(ctx->filterExpression()
|
||||
->idInColl()
|
||||
->variable()
|
||||
->accept(this)
|
||||
.as<std::string>());
|
||||
Expression *list_expr =
|
||||
ctx->filterExpression()->idInColl()->expression()->accept(this);
|
||||
if (!ctx->filterExpression()->where()) {
|
||||
throw SyntaxException("NONE(...) requires a WHERE predicate.");
|
||||
}
|
||||
Where *where = ctx->filterExpression()->where()->accept(this);
|
||||
return static_cast<Expression *>(
|
||||
storage_->Create<None>(ident, list_expr, where));
|
||||
} else if (ctx->REDUCE()) {
|
||||
auto *accumulator = storage_->Create<Identifier>(
|
||||
ctx->reduceExpression()->accumulator->accept(this).as<std::string>());
|
||||
|
@ -52,6 +52,7 @@ class ExpressionPrettyPrinter : public ExpressionVisitor<void> {
|
||||
void Visit(All &op) override;
|
||||
void Visit(Single &op) override;
|
||||
void Visit(Any &op) override;
|
||||
void Visit(None &op) override;
|
||||
void Visit(Identifier &op) override;
|
||||
void Visit(PrimitiveLiteral &op) override;
|
||||
void Visit(PropertyLookup &op) override;
|
||||
@ -292,6 +293,11 @@ void ExpressionPrettyPrinter::Visit(Any &op) {
|
||||
op.where_->expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(None &op) {
|
||||
PrintOperator(out_, "None", op.identifier_, op.list_expression_,
|
||||
op.where_->expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(Identifier &op) {
|
||||
PrintOperator(out_, "Identifier", op.name_);
|
||||
}
|
||||
|
@ -360,6 +360,12 @@ bool SymbolGenerator::PreVisit(Any &any) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(None &none) {
|
||||
none.list_expression_->Accept(*this);
|
||||
VisitWithIdentifiers(none.where_->expression_, {none.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Reduce &reduce) {
|
||||
reduce.initializer_->Accept(*this);
|
||||
reduce.list_->Accept(*this);
|
||||
|
@ -59,6 +59,7 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
|
||||
bool PreVisit(All &) override;
|
||||
bool PreVisit(Single &) override;
|
||||
bool PreVisit(Any &) override;
|
||||
bool PreVisit(None &) override;
|
||||
bool PreVisit(Reduce &) override;
|
||||
bool PreVisit(Extract &) override;
|
||||
|
||||
|
@ -558,6 +558,41 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Visit(None &none) override {
|
||||
auto list_value = none.list_expression_->Accept(*this);
|
||||
if (list_value.IsNull()) {
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
throw QueryRuntimeException("NONE expected a list, got {}.",
|
||||
list_value.type());
|
||||
}
|
||||
const auto &list = list_value.ValueList();
|
||||
const auto &symbol = symbol_table_->at(*none.identifier_);
|
||||
bool has_value = false;
|
||||
for (const auto &element : list) {
|
||||
frame_->at(symbol) = element;
|
||||
auto result = none.where_->expression_->Accept(*this);
|
||||
if (!result.IsNull() && result.type() != TypedValue::Type::Bool) {
|
||||
throw QueryRuntimeException(
|
||||
"Predicate of NONE must evaluate to boolean, got {}.",
|
||||
result.type());
|
||||
}
|
||||
if (!result.IsNull()) {
|
||||
has_value = true;
|
||||
if (result.ValueBool()) {
|
||||
return TypedValue(false, ctx_->memory);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return Null if all elements are Null
|
||||
if (!has_value) {
|
||||
return TypedValue(ctx_->memory);
|
||||
} else {
|
||||
return TypedValue(true, ctx_->memory);
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Visit(ParameterLookup ¶m_lookup) override {
|
||||
return TypedValue(
|
||||
ctx_->parameters.AtTokenPosition(param_lookup.token_position_),
|
||||
|
@ -44,6 +44,13 @@ class UsedSymbolsCollector : public HierarchicalTreeVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(None &none) override {
|
||||
// Remove the symbol which is bound by none, because we are only interested
|
||||
// in free (unbound) symbols.
|
||||
symbols_.erase(symbol_table_.at(*none.identifier_));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Reduce &reduce) override {
|
||||
// Remove the symbols bound by reduce, because we are only interested
|
||||
// in free (unbound) symbols.
|
||||
|
@ -180,6 +180,21 @@ class ReturnBodyContext : public HierarchicalTreeVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(None &none) override {
|
||||
// Remove the symbol which is bound by none, because we are only interested
|
||||
// in free (unbound) symbols.
|
||||
used_symbols_.erase(symbol_table_.at(*none.identifier_));
|
||||
CHECK(has_aggregation_.size() >= 3U)
|
||||
<< "Expected 3 has_aggregation_ flags for NONE arguments";
|
||||
bool has_aggr = false;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
has_aggr = has_aggr || has_aggregation_.back();
|
||||
has_aggregation_.pop_back();
|
||||
}
|
||||
has_aggregation_.emplace_back(has_aggr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Reduce &reduce) override {
|
||||
// Remove the symbols bound by reduce, because we are only interested
|
||||
// in free (unbound) symbols.
|
||||
|
@ -785,7 +785,79 @@ Feature: Functions
|
||||
| a |
|
||||
| null |
|
||||
|
||||
|
||||
Scenario: None test 01:
|
||||
When executing query:
|
||||
"""
|
||||
RETURN none(x IN [1, 2, 3] WHERE x < 1) AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| true |
|
||||
|
||||
Scenario: None test 02:
|
||||
When executing query:
|
||||
"""
|
||||
RETURN none(x IN [1, 2, 3] WHERE x = 1) AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| false |
|
||||
|
||||
Scenario: None test 03:
|
||||
When executing query:
|
||||
"""
|
||||
RETURN none(x IN ["a", "b", "c"] WHERE x = 1) AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| true |
|
||||
|
||||
Scenario: None test 04:
|
||||
When executing query:
|
||||
"""
|
||||
RETURN none(x IN [Null, Null, Null] WHERE x = 0) AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
|
||||
Scenario: None test 05:
|
||||
When executing query:
|
||||
"""
|
||||
RETURN none(x IN [Null, Null, 0] WHERE x > 0) AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| true |
|
||||
|
||||
Scenario: None test 06:
|
||||
When executing query:
|
||||
"""
|
||||
RETURN none(x IN [Null, Null, 0] WHERE x = 0) AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| false |
|
||||
|
||||
Scenario: None test 07:
|
||||
When executing query:
|
||||
"""
|
||||
RETURN none(x IN [Null, Null, Null] WHERE x = Null) AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
|
||||
Scenario: None test 08:
|
||||
When executing query:
|
||||
"""
|
||||
RETURN none(x IN ["a", "b", "c"] WHERE x = Null) AS a
|
||||
"""
|
||||
Then the result should be:
|
||||
| a |
|
||||
| null |
|
||||
|
||||
|
||||
Scenario: Reduce test 01:
|
||||
When executing query:
|
||||
"""
|
||||
|
@ -642,6 +642,9 @@ auto GetMerge(AstStorage &storage, Pattern *pattern, OnMatch on_match,
|
||||
#define ANY(variable, list, where) \
|
||||
storage.Create<query::Any>(storage.Create<query::Identifier>(variable), \
|
||||
list, where)
|
||||
#define NONE(variable, list, where) \
|
||||
storage.Create<query::None>(storage.Create<query::Identifier>(variable), \
|
||||
list, where)
|
||||
#define REDUCE(accumulator, initializer, variable, list, expr) \
|
||||
storage.Create<query::Reduce>( \
|
||||
storage.Create<query::Identifier>(accumulator), initializer, \
|
||||
|
@ -836,6 +836,74 @@ TEST_F(ExpressionEvaluatorTest, FunctionAnyWhereWrongType) {
|
||||
EXPECT_THROW(Eval(any), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorTest, FunctionNone1) {
|
||||
AstStorage storage;
|
||||
auto *ident_x = IDENT("x");
|
||||
auto *none =
|
||||
NONE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(0))));
|
||||
const auto x_sym = symbol_table.CreateSymbol("x", true);
|
||||
none->identifier_->MapTo(x_sym);
|
||||
ident_x->MapTo(x_sym);
|
||||
auto value = Eval(none);
|
||||
ASSERT_TRUE(value.IsBool());
|
||||
EXPECT_TRUE(value.ValueBool());
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorTest, FunctionNone2) {
|
||||
AstStorage storage;
|
||||
auto *ident_x = IDENT("x");
|
||||
auto *none =
|
||||
NONE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1))));
|
||||
const auto x_sym = symbol_table.CreateSymbol("x", true);
|
||||
none->identifier_->MapTo(x_sym);
|
||||
ident_x->MapTo(x_sym);
|
||||
auto value = Eval(none);
|
||||
ASSERT_TRUE(value.IsBool());
|
||||
EXPECT_FALSE(value.ValueBool());
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorTest, FunctionNoneNullList) {
|
||||
AstStorage storage;
|
||||
auto *none =
|
||||
NONE("x", LITERAL(storage::PropertyValue()), WHERE(LITERAL(true)));
|
||||
const auto x_sym = symbol_table.CreateSymbol("x", true);
|
||||
none->identifier_->MapTo(x_sym);
|
||||
auto value = Eval(none);
|
||||
EXPECT_TRUE(value.IsNull());
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorTest, FunctionNoneNullElementInList1) {
|
||||
AstStorage storage;
|
||||
auto *ident_x = IDENT("x");
|
||||
auto *any = NONE("x", LIST(LITERAL(1), LITERAL(storage::PropertyValue())),
|
||||
WHERE(EQ(ident_x, LITERAL(0))));
|
||||
const auto x_sym = symbol_table.CreateSymbol("x", true);
|
||||
any->identifier_->MapTo(x_sym);
|
||||
ident_x->MapTo(x_sym);
|
||||
auto value = Eval(any);
|
||||
EXPECT_TRUE(value.ValueBool());
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorTest, FunctionNoneNullElementInList2) {
|
||||
AstStorage storage;
|
||||
auto *ident_x = IDENT("x");
|
||||
auto *none = NONE("x", LIST(LITERAL(0), LITERAL(storage::PropertyValue())),
|
||||
WHERE(EQ(ident_x, LITERAL(0))));
|
||||
const auto x_sym = symbol_table.CreateSymbol("x", true);
|
||||
none->identifier_->MapTo(x_sym);
|
||||
ident_x->MapTo(x_sym);
|
||||
auto value = Eval(none);
|
||||
EXPECT_FALSE(value.ValueBool());
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorTest, FunctionNoneWhereWrongType) {
|
||||
AstStorage storage;
|
||||
auto *none = NONE("x", LIST(LITERAL(1)), WHERE(LITERAL(2)));
|
||||
const auto x_sym = symbol_table.CreateSymbol("x", true);
|
||||
none->identifier_->MapTo(x_sym);
|
||||
EXPECT_THROW(Eval(none), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorTest, FunctionReduce) {
|
||||
AstStorage storage;
|
||||
auto *ident_sum = IDENT("sum");
|
||||
|
Loading…
Reference in New Issue
Block a user