diff --git a/src/context/ast/QueryAstContext.h b/src/context/ast/QueryAstContext.h index 93d6fa88bd4d7101e0b73f56558f34e17008d57d..90522856ee0042016e4fd5a4598e8c901457fc48 100644 --- a/src/context/ast/QueryAstContext.h +++ b/src/context/ast/QueryAstContext.h @@ -76,7 +76,7 @@ struct CypherClauseContextBase : AstContext { struct WhereClauseContext final : CypherClauseContextBase { WhereClauseContext() : CypherClauseContextBase(CypherClauseKind::kWhere) {} - std::unique_ptr<Expression> filter; + Expression* filter; std::unordered_map<std::string, AliasType>* aliasesUsed{nullptr}; }; diff --git a/src/planner/match/PropIndexSeek.cpp b/src/planner/match/PropIndexSeek.cpp index 7c11daaa43dd635e4fc5669dca60e7469d980bc5..459fb0ee093c6a8e325823e595ce8dd3335d7d3e 100644 --- a/src/planner/match/PropIndexSeek.cpp +++ b/src/planner/match/PropIndexSeek.cpp @@ -34,7 +34,7 @@ bool PropIndexSeek::matchNode(NodeContext* nodeCtx) { if (matchClauseCtx->where != nullptr && matchClauseCtx->where->filter != nullptr) { filter = MatchSolver::makeIndexFilter(*node.labels.back(), *node.alias, - matchClauseCtx->where->filter.get(), + matchClauseCtx->where->filter, matchClauseCtx->qctx); } if (filter == nullptr) { diff --git a/src/planner/match/WhereClausePlanner.cpp b/src/planner/match/WhereClausePlanner.cpp index e998758bf513301c1b257c15c33314d841dee5ce..5e711b6a5e1f3e0899ad3e976d740578a282e8c8 100644 --- a/src/planner/match/WhereClausePlanner.cpp +++ b/src/planner/match/WhereClausePlanner.cpp @@ -20,7 +20,7 @@ StatusOr<SubPlan> WhereClausePlanner::transform(CypherClauseContextBase* ctx) { auto* wctx = static_cast<WhereClauseContext*>(ctx); if (wctx->filter) { SubPlan wherePlan; - auto* newFilter = MatchSolver::doRewrite(*wctx->aliasesUsed, wctx->filter.get()); + auto* newFilter = MatchSolver::doRewrite(*wctx->aliasesUsed, wctx->filter); wctx->qctx->objPool()->add(newFilter); wherePlan.root = Filter::make(wctx->qctx, nullptr, newFilter, true); wherePlan.tail = wherePlan.root; diff --git a/src/util/ExpressionUtils.cpp b/src/util/ExpressionUtils.cpp index a39d4dc82d5f01ba9c973d3a3e07869cd3445b4f..7cd518501a19ad518c3180e319f5aa8e56f5c07b 100644 --- a/src/util/ExpressionUtils.cpp +++ b/src/util/ExpressionUtils.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. +/* Copyright (c) 2021 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. @@ -11,7 +11,7 @@ #include "common/expression/PropertyExpression.h" #include "common/function/AggFunctionManager.h" #include "visitor/FoldConstantExprVisitor.h" -#include "visitor/EvaluableExprVisitor.h" +#include "visitor/RewriteUnaryNotExprVisitor.h" namespace nebula { namespace graph { @@ -26,9 +26,16 @@ std::unique_ptr<Expression> ExpressionUtils::foldConstantExpr(const Expression * return newExpr; } -Expression* ExpressionUtils::pullAnds(Expression *expr) { +Expression *ExpressionUtils::reduceUnaryNotExpr(const Expression *expr, ObjectPool *objPool) { + auto newExpr = expr->clone(); + RewriteUnaryNotExprVisitor visitor(objPool); + objPool->add(newExpr.release())->accept(&visitor); + return visitor.getExpr(); +} + +Expression *ExpressionUtils::pullAnds(Expression *expr) { DCHECK(expr->kind() == Expression::Kind::kLogicalAnd); - auto *logic = static_cast<LogicalExpression*>(expr); + auto *logic = static_cast<LogicalExpression *>(expr); std::vector<std::unique_ptr<Expression>> operands; pullAndsImpl(logic, operands); logic->setOperands(std::move(operands)); @@ -236,10 +243,89 @@ Status ExpressionUtils::checkAggExpr(const AggregateExpression* aggExpr) { return Status::OK(); } -bool ExpressionUtils::isEvaluableExpr(const Expression* expr) { - EvaluableExprVisitor visitor; - const_cast<Expression*>(expr)->accept(&visitor); - return visitor.ok(); +std::unique_ptr<RelationalExpression> ExpressionUtils::reverseRelExpr(RelationalExpression *expr) { + auto left = static_cast<RelationalExpression *>(expr)->left(); + auto right = static_cast<RelationalExpression *>(expr)->right(); + auto negatedKind = getNegatedRelExprKind(expr->kind()); + + return std::make_unique<RelationalExpression>( + negatedKind, left->clone().release(), right->clone().release()); +} + +Expression::Kind ExpressionUtils::getNegatedRelExprKind(const Expression::Kind kind) { + switch (kind) { + case Expression::Kind::kRelEQ: + return Expression::Kind::kRelNE; + case Expression::Kind::kRelNE: + return Expression::Kind::kRelEQ; + case Expression::Kind::kRelLT: + return Expression::Kind::kRelGE; + case Expression::Kind::kRelLE: + return Expression::Kind::kRelGT; + case Expression::Kind::kRelGT: + return Expression::Kind::kRelLE; + case Expression::Kind::kRelGE: + return Expression::Kind::kRelLT; + case Expression::Kind::kRelIn: + return Expression::Kind::kRelNotIn; + case Expression::Kind::kRelNotIn: + return Expression::Kind::kRelIn; + case Expression::Kind::kContains: + return Expression::Kind::kNotContains; + case Expression::Kind::kNotContains: + return Expression::Kind::kContains; + case Expression::Kind::kStartsWith: + return Expression::Kind::kNotStartsWith; + case Expression::Kind::kNotStartsWith: + return Expression::Kind::kStartsWith; + case Expression::Kind::kEndsWith: + return Expression::Kind::kNotEndsWith; + case Expression::Kind::kNotEndsWith: + return Expression::Kind::kEndsWith; + default: + LOG(FATAL) << "Invalid relational expression kind: " << static_cast<uint8_t>(kind); + break; + } +} + +std::unique_ptr<LogicalExpression> ExpressionUtils::reverseLogicalExpr(LogicalExpression *expr) { + DCHECK(expr->isLogicalExpr()); + + std::vector<std::unique_ptr<Expression>> operands; + Expression *newExpr; + if (expr->kind() == Expression::Kind::kLogicalAnd) { + newExpr = ExpressionUtils::pullAnds(expr); + } else { + newExpr = ExpressionUtils::pullOrs(expr); + } + + auto &flattenOperands = static_cast<LogicalExpression *>(newExpr)->operands(); + auto negatedKind = getNegatedLogicalExprKind(expr->kind()); + auto logic = std::make_unique<LogicalExpression>(negatedKind); + + // negate each item in the operands list + for (auto &operand : flattenOperands) { + auto tempExpr = + std::make_unique<UnaryExpression>(Expression::Kind::kUnaryNot, operand.release()); + operands.emplace_back(std::move(tempExpr)); + } + logic->setOperands(std::move(operands)); + return logic; +} + +Expression::Kind ExpressionUtils::getNegatedLogicalExprKind(const Expression::Kind kind) { + switch (kind) { + case Expression::Kind::kLogicalAnd: + return Expression::Kind::kLogicalOr; + case Expression::Kind::kLogicalOr: + return Expression::Kind::kLogicalAnd; + case Expression::Kind::kLogicalXor: + LOG(FATAL) << "Unsupported logical expression kind: " << static_cast<uint8_t>(kind); + break; + default: + LOG(FATAL) << "Invalid logical expression kind: " << static_cast<uint8_t>(kind); + break; + } } } // namespace graph } // namespace nebula diff --git a/src/util/ExpressionUtils.h b/src/util/ExpressionUtils.h index aa4c5c51f4400f919eaa535aafbfafba77562436..9a18f1267c66f2e83e82a8ccefa1cb8b99d7871e 100644 --- a/src/util/ExpressionUtils.h +++ b/src/util/ExpressionUtils.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. +/* Copyright (c) 2021 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. @@ -7,6 +7,7 @@ #ifndef _UTIL_EXPRESSION_UTILS_H_ #define _UTIL_EXPRESSION_UTILS_H_ +#include "common/base/ObjectPool.h" #include "common/base/Status.h" #include "common/expression/AggregateExpression.h" #include "common/expression/BinaryExpression.h" @@ -17,6 +18,7 @@ #include "common/expression/TypeCastingExpression.h" #include "common/expression/UnaryExpression.h" #include "visitor/CollectAllExprsVisitor.h" +#include "visitor/EvaluableExprVisitor.h" #include "visitor/FindAnyExprVisitor.h" #include "visitor/RewriteVisitor.h" @@ -73,6 +75,7 @@ public: return collectAll(expr, {Expression::Kind::kInputProperty, Expression::Kind::kVarProperty}); } + // **Expression type check** static bool isConstExpr(const Expression* expr) { return !hasAny(expr, {Expression::Kind::kInputProperty, @@ -108,6 +111,12 @@ public: return RewriteVisitor::transform(expr, std::move(matcher), std::move(rewriter)); } + static bool isEvaluableExpr(const Expression* expr) { + EvaluableExprVisitor visitor; + const_cast<Expression*>(expr)->accept(&visitor); + return visitor.ok(); + } + // rewrite LabelAttr to EdgeProp (just for nGql) static Expression* rewriteLabelAttr2EdgeProp(const Expression* expr) { auto matcher = [](const Expression* e) -> bool { @@ -146,9 +155,25 @@ public: Expression::Kind::kMod}); } + // **Expression Transformation** // Clone and fold constant expression static std::unique_ptr<Expression> foldConstantExpr(const Expression* expr); + // Clone and reduce constant expression + static Expression* reduceUnaryNotExpr(const Expression* expr, ObjectPool* objPool); + + // Negate the given logical expr: (A && B) -> (!A || !B) + static std::unique_ptr<LogicalExpression> reverseLogicalExpr(LogicalExpression* expr); + + // Negate the given relational expr: (A > B) -> (A <= B) + static std::unique_ptr<RelationalExpression> reverseRelExpr(RelationalExpression* expr); + + // Return the negation of the given relational kind + static Expression::Kind getNegatedRelExprKind(const Expression::Kind kind); + + // Return the negation of the given logical kind + static Expression::Kind getNegatedLogicalExprKind(const Expression::Kind kind); + static Expression* pullAnds(Expression* expr); static void pullAndsImpl(LogicalExpression* expr, std::vector<std::unique_ptr<Expression>>& operands); @@ -179,8 +204,6 @@ public: static std::vector<std::unique_ptr<Expression>> expandImplOr(const Expression* expr); static Status checkAggExpr(const AggregateExpression* aggExpr); - - static bool isEvaluableExpr(const Expression* expr); }; } // namespace graph diff --git a/src/validator/MatchValidator.cpp b/src/validator/MatchValidator.cpp index f13930f71e013c8c9cfe03a841c7c15bd6a50aea..32c45e440d9774c10ab115abbef7ff1190d51eb5 100644 --- a/src/validator/MatchValidator.cpp +++ b/src/validator/MatchValidator.cpp @@ -9,6 +9,7 @@ #include "planner/match/MatchSolver.h" #include "util/ExpressionUtils.h" #include "visitor/RewriteVisitor.h" +#include "visitor/RewriteUnaryNotExprVisitor.h" namespace nebula { namespace graph { @@ -291,9 +292,13 @@ Status MatchValidator::buildEdgeInfo(const MatchPath *path, Status MatchValidator::validateFilter(const Expression *filter, WhereClauseContext &whereClauseCtx) const { - whereClauseCtx.filter = ExpressionUtils::foldConstantExpr(filter); + // Fold constant expr + auto newFilter = ExpressionUtils::foldConstantExpr(filter); + // Reduce Unary expr + auto pool = whereClauseCtx.qctx->objPool(); + whereClauseCtx.filter = ExpressionUtils::reduceUnaryNotExpr(newFilter.get(), pool); - auto typeStatus = deduceExprType(whereClauseCtx.filter.get()); + auto typeStatus = deduceExprType(whereClauseCtx.filter); NG_RETURN_IF_ERROR(typeStatus); auto type = typeStatus.value(); if (type != Value::Type::BOOL && type != Value::Type::NULLVALUE && @@ -304,7 +309,7 @@ Status MatchValidator::validateFilter(const Expression *filter, return Status::SemanticError(ss.str()); } - NG_RETURN_IF_ERROR(validateAliases({whereClauseCtx.filter.get()}, whereClauseCtx.aliasesUsed)); + NG_RETURN_IF_ERROR(validateAliases({whereClauseCtx.filter}, whereClauseCtx.aliasesUsed)); return Status::OK(); } diff --git a/src/visitor/CMakeLists.txt b/src/visitor/CMakeLists.txt index 0281c8daa4eadd0118e4c1f87f8ce812d81b6094..b5a6b2ed835b511e2f4244718f9307e463594e3c 100644 --- a/src/visitor/CMakeLists.txt +++ b/src/visitor/CMakeLists.txt @@ -17,6 +17,7 @@ nebula_add_library( RewriteSymExprVisitor.cpp RewriteVisitor.cpp VidExtractVisitor.cpp + RewriteUnaryNotExprVisitor.cpp ) nebula_add_subdirectory(test) diff --git a/src/visitor/RewriteUnaryNotExprVisitor.cpp b/src/visitor/RewriteUnaryNotExprVisitor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..54008d1ee7baab1f571077e51bdcaa319b1e9708 --- /dev/null +++ b/src/visitor/RewriteUnaryNotExprVisitor.cpp @@ -0,0 +1,247 @@ +/* Copyright (c) 2021 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. + */ + +#include "visitor/RewriteUnaryNotExprVisitor.h" + +#include "context/QueryExpressionContext.h" +#include "util/ExpressionUtils.h" +namespace nebula { +namespace graph { + +RewriteUnaryNotExprVisitor::RewriteUnaryNotExprVisitor(ObjectPool *objPool) : pool_(objPool) {} + +void RewriteUnaryNotExprVisitor::visit(ConstantExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(UnaryExpression *expr) { + auto operand = expr->operand(); + if (isUnaryNotExpr(expr)) { + if (isUnaryNotExpr(operand)) { + static_cast<UnaryExpression *>(operand)->operand()->accept(this); + return; + } else if (operand->isRelExpr() && operand->kind() != Expression::Kind::kRelREG) { + auto reversedExpt = + ExpressionUtils::reverseRelExpr(static_cast<RelationalExpression *>(operand)); + expr_ = pool_->add(reversedExpt.release()); + return; + } else if (operand->isLogicalExpr()) { + auto reducedExpr = + ExpressionUtils::reverseLogicalExpr(static_cast<LogicalExpression *>(operand)); + auto reducedLogicalExpr = pool_->add(reducedExpr.release()); + reducedLogicalExpr->accept(this); + return; + } + } + operand->accept(this); + + if (operand != expr_) { + expr->setOperand(expr_->clone().release()); + } + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(TypeCastingExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(LabelExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(LabelAttributeExpression *expr) { + expr_ = expr; +} + +// binary expression +void RewriteUnaryNotExprVisitor::visit(ArithmeticExpression *expr) { + visitBinaryExpr(expr); +} + +void RewriteUnaryNotExprVisitor::visit(RelationalExpression *expr) { + visitBinaryExpr(expr); +} + +void RewriteUnaryNotExprVisitor::visit(SubscriptExpression *expr) { + visitBinaryExpr(expr); +} + +void RewriteUnaryNotExprVisitor::visit(AttributeExpression *expr) { + visitBinaryExpr(expr); +} + +void RewriteUnaryNotExprVisitor::visit(LogicalExpression *expr) { + auto &operands = expr->operands(); + for (auto i = 0u; i < operands.size(); i++) { + operands[i]->accept(this); + if (expr_ != operands[i].get()) { + expr->setOperand(i, expr_->clone().release()); + } + } + expr_ = expr; +} + +// Rewrite Unary expresssion to Binary expression +void RewriteUnaryNotExprVisitor::visitBinaryExpr(BinaryExpression *expr) { + expr->left()->accept(this); + if (expr_ != expr->left()) { + expr->setLeft(expr_->clone().release()); + } + expr->right()->accept(this); + if (expr_ != expr->right()) { + expr->setRight(expr_->clone().release()); + } + expr_ = expr; +} + +// function call +void RewriteUnaryNotExprVisitor::visit(FunctionCallExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(AggregateExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(UUIDExpression *expr) { + expr_ = expr; +} + +// variable expression +void RewriteUnaryNotExprVisitor::visit(VariableExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(VersionedVariableExpression *expr) { + expr_ = expr; +} + +// container expression +void RewriteUnaryNotExprVisitor::visit(ListExpression *expr) { + auto &items = expr->items(); + for (auto i = 0u; i < items.size(); ++i) { + auto item = items[i].get(); + item->accept(this); + if (expr_ != item) { + expr->setItem(i, expr_->clone()); + } + } + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(SetExpression *expr) { + auto &items = expr->items(); + for (auto i = 0u; i < items.size(); ++i) { + auto item = items[i].get(); + item->accept(this); + if (expr_ != item) { + expr->setItem(i, expr_->clone()); + } + } + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(MapExpression *expr) { + auto &items = expr->items(); + for (auto i = 0u; i < items.size(); ++i) { + auto &pair = items[i]; + auto item = const_cast<Expression *>(pair.second.get()); + item->accept(this); + if (expr_ != item) { + auto key = std::make_unique<std::string>(*pair.first); + auto val = expr_->clone(); + expr->setItem(i, std::make_pair(std::move(key), std::move(val))); + } + } + expr_ = expr; +} + +// property Expression +void RewriteUnaryNotExprVisitor::visit(TagPropertyExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(EdgePropertyExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(InputPropertyExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(VariablePropertyExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(DestPropertyExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(SourcePropertyExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(EdgeSrcIdExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(EdgeTypeExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(EdgeRankExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(EdgeDstIdExpression *expr) { + expr_ = expr; +} + +// vertex/edge expression +void RewriteUnaryNotExprVisitor::visit(VertexExpression *expr) { + expr_ = expr; +} + +void RewriteUnaryNotExprVisitor::visit(EdgeExpression *expr) { + expr_ = expr; +} + +// case expression +void RewriteUnaryNotExprVisitor::visit(CaseExpression *expr) { + expr_ = expr; +} + +// path build expression +void RewriteUnaryNotExprVisitor::visit(PathBuildExpression *expr) { + expr_ = expr; +} + +// column expression +void RewriteUnaryNotExprVisitor::visit(ColumnExpression *expr) { + expr_ = expr; +} + +// predicate expression +void RewriteUnaryNotExprVisitor::visit(PredicateExpression *expr) { + expr_ = expr; +} + +// list comprehension expression +void RewriteUnaryNotExprVisitor::visit(ListComprehensionExpression *expr) { + expr_ = expr; +} + +// reduce expression +void RewriteUnaryNotExprVisitor::visit(ReduceExpression *expr) { + expr_ = expr; +} + +bool RewriteUnaryNotExprVisitor::isUnaryNotExpr(const Expression *expr) { + return expr->kind() == Expression::Kind::kUnaryNot; +} + +} // namespace graph +} // namespace nebula diff --git a/src/visitor/RewriteUnaryNotExprVisitor.h b/src/visitor/RewriteUnaryNotExprVisitor.h new file mode 100644 index 0000000000000000000000000000000000000000..547f47465a4856f589ef14ac31c46c107e87396c --- /dev/null +++ b/src/visitor/RewriteUnaryNotExprVisitor.h @@ -0,0 +1,93 @@ +/* Copyright (c) 2021 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. + */ + +#ifndef VISITOR_REWRITEUNARYNOTEXPRVISITOR_H_ +#define VISITOR_REWRITEUNARYNOTEXPRVISITOR_H_ + +#include "common/base/ObjectPool.h" +#include "common/expression/ExprVisitor.h" +#include "visitor/ExprVisitorImpl.h" + +namespace nebula { +namespace graph { + +class RewriteUnaryNotExprVisitor final : public ExprVisitorImpl { +public: + explicit RewriteUnaryNotExprVisitor(ObjectPool *objPool); + + bool ok() const override { + return true; + } + + Expression *getExpr() { + return expr_; + } + +private: + using ExprVisitorImpl::visit; + + void visit(ConstantExpression *expr) override; + void visit(UnaryExpression *expr) override; + void visit(TypeCastingExpression *expr) override; + void visit(LabelExpression *expr) override; + void visit(LabelAttributeExpression *expr) override; + // binary expression + void visit(ArithmeticExpression *expr) override; + void visit(RelationalExpression *expr) override; + void visit(SubscriptExpression *expr) override; + void visit(AttributeExpression *expr) override; + void visit(LogicalExpression *expr) override; + // function call + void visit(FunctionCallExpression *expr) override; + void visit(AggregateExpression *expr) override; + void visit(UUIDExpression *expr) override; + // variable expression + void visit(VariableExpression *expr) override; + void visit(VersionedVariableExpression *expr) override; + // container expression + void visit(ListExpression *expr) override; + void visit(SetExpression *expr) override; + void visit(MapExpression *expr) override; + // property Expression + void visit(TagPropertyExpression *expr) override; + void visit(EdgePropertyExpression *expr) override; + void visit(InputPropertyExpression *expr) override; + void visit(VariablePropertyExpression *expr) override; + void visit(DestPropertyExpression *expr) override; + void visit(SourcePropertyExpression *expr) override; + void visit(EdgeSrcIdExpression *expr) override; + void visit(EdgeTypeExpression *expr) override; + void visit(EdgeRankExpression *expr) override; + void visit(EdgeDstIdExpression *expr) override; + // vertex/edge expression + void visit(VertexExpression *expr) override; + void visit(EdgeExpression *expr) override; + // case expression + void visit(CaseExpression *expr) override; + // path build expression + void visit(PathBuildExpression *expr) override; + // column expression + void visit(ColumnExpression *expr) override; + // predicate expression + void visit(PredicateExpression *expr) override; + // list comprehension expression + void visit(ListComprehensionExpression *) override; + // reduce expression + void visit(ReduceExpression *expr) override; + + void visitBinaryExpr(BinaryExpression *expr) override; + +private: + bool isUnaryNotExpr(const Expression *expr); + + Expression *expr_; + ObjectPool *pool_; +}; + +} // namespace graph +} // namespace nebula + +#endif // VISITOR_REWRITEUNARYNOTEXPRVISITOR_H_ diff --git a/src/visitor/test/CMakeLists.txt b/src/visitor/test/CMakeLists.txt index d8aa20abcadaffd26687d8d7fec93e29fee0e2e2..bb2eb7503a8bb44088c37dbd54fa028d5e2be54c 100644 --- a/src/visitor/test/CMakeLists.txt +++ b/src/visitor/test/CMakeLists.txt @@ -10,6 +10,7 @@ nebula_add_test( TestMain.cpp FoldConstantExprVisitorTest.cpp DeduceTypeVisitorTest.cpp + RewriteUnaryNotExprVisitorTest.cpp OBJECTS $<TARGET_OBJECTS:mock_schema_obj> $<TARGET_OBJECTS:util_obj> diff --git a/src/visitor/test/FoldConstantExprVisitorTest.cpp b/src/visitor/test/FoldConstantExprVisitorTest.cpp index 7123e96bd27d6359fc8817d8ff0f8eb6fae55739..a23c2b76c47e99519e4cae378a91879bbfe949df 100644 --- a/src/visitor/test/FoldConstantExprVisitorTest.cpp +++ b/src/visitor/test/FoldConstantExprVisitorTest.cpp @@ -5,110 +5,21 @@ */ #include "visitor/FoldConstantExprVisitor.h" +#include "visitor/test/VisitorTestBase.h" #include <gtest/gtest.h> #include "common/base/ObjectPool.h" -using Type = nebula::Value::Type; - namespace nebula { namespace graph { -class FoldConstantExprVisitorTest : public ::testing::Test { +class FoldConstantExprVisitorTest : public ValidatorTestBase { public: void TearDown() override { pool.clear(); } - static ConstantExpression *constantExpr(Value value) { - return new ConstantExpression(std::move(value)); - } - - static ArithmeticExpression *addExpr(Expression *lhs, Expression *rhs) { - return new ArithmeticExpression(Expression::Kind::kAdd, lhs, rhs); - } - - static ArithmeticExpression *minusExpr(Expression *lhs, Expression *rhs) { - return new ArithmeticExpression(Expression::Kind::kMinus, lhs, rhs); - } - - static RelationalExpression *gtExpr(Expression *lhs, Expression *rhs) { - return new RelationalExpression(Expression::Kind::kRelGT, lhs, rhs); - } - - static RelationalExpression *eqExpr(Expression *lhs, Expression *rhs) { - return new RelationalExpression(Expression::Kind::kRelEQ, lhs, rhs); - } - - static TypeCastingExpression *castExpr(Type type, Expression *expr) { - return new TypeCastingExpression(type, expr); - } - - static UnaryExpression *notExpr(Expression *expr) { - return new UnaryExpression(Expression::Kind::kUnaryNot, expr); - } - - static LogicalExpression *andExpr(Expression *lhs, Expression *rhs) { - return new LogicalExpression(Expression::Kind::kLogicalAnd, lhs, rhs); - } - - static LogicalExpression *orExpr(Expression *lhs, Expression *rhs) { - return new LogicalExpression(Expression::Kind::kLogicalOr, lhs, rhs); - } - - static ListExpression *listExpr(std::initializer_list<Expression *> exprs) { - auto exprList = new ExpressionList; - for (auto expr : exprs) { - exprList->add(expr); - } - return new ListExpression(exprList); - } - - static SetExpression *setExpr(std::initializer_list<Expression *> exprs) { - auto exprList = new ExpressionList; - for (auto expr : exprs) { - exprList->add(expr); - } - return new SetExpression(exprList); - } - - static MapExpression *mapExpr( - std::initializer_list<std::pair<std::string, Expression *>> exprs) { - auto mapItemList = new MapItemList; - for (auto expr : exprs) { - mapItemList->add(new std::string(expr.first), expr.second); - } - return new MapExpression(mapItemList); - } - - static SubscriptExpression *subExpr(Expression *lhs, Expression *rhs) { - return new SubscriptExpression(lhs, rhs); - } - - static FunctionCallExpression *fnExpr(std::string fn, - std::initializer_list<Expression *> args) { - auto argsList = new ArgumentList; - for (auto arg : args) { - argsList->addArgument(std::unique_ptr<Expression>(arg)); - } - return new FunctionCallExpression(new std::string(std::move(fn)), argsList); - } - - static VariableExpression *varExpr(const std::string &name) { - return new VariableExpression(new std::string(name)); - } - - static CaseExpression *caseExpr(Expression *cond, Expression *defaltResult, - Expression *when, Expression *then) { - auto caseList = new CaseList; - caseList->add(when, then); - auto expr = new CaseExpression(caseList); - expr->setCondition(cond); - expr->setDefault(defaltResult); - return expr; - } - protected: ObjectPool pool; }; diff --git a/src/visitor/test/RewriteUnaryNotExprVisitorTest.cpp b/src/visitor/test/RewriteUnaryNotExprVisitorTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b76615f02206fe0958ed056e43fffd8538aca4f0 --- /dev/null +++ b/src/visitor/test/RewriteUnaryNotExprVisitorTest.cpp @@ -0,0 +1,266 @@ + +/* Copyright (c) 2021 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. + */ + +#include "visitor/RewriteUnaryNotExprVisitor.h" +#include "visitor/test/VisitorTestBase.h" + +#include <gtest/gtest.h> + +#include "common/base/ObjectPool.h" + +namespace nebula { +namespace graph { +class RewriteUnaryNotExprVisitorTest : public ValidatorTestBase { +public: + void TearDown() override { + pool.clear(); + } + +protected: + ObjectPool pool; +}; + +TEST_F(RewriteUnaryNotExprVisitorTest, TestNestedMultipleUnaryNotExpr) { + // !!(5 == 10) => (5 == 10) + { + auto expr = pool.add(notExpr(notExpr(eqExpr(constantExpr(5), constantExpr(10))))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(eqExpr(constantExpr(5), constantExpr(10))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + // !!!!(5 == 10) => (5 == 10) + { + auto expr = + pool.add(notExpr(notExpr(notExpr(notExpr(eqExpr(constantExpr(5), constantExpr(10))))))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(eqExpr(constantExpr(5), constantExpr(10))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + // !!!(5 == 10) => (5 != 10) + { + auto expr = pool.add(notExpr(notExpr(notExpr(eqExpr(constantExpr(5), constantExpr(10)))))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(neExpr(constantExpr(5), constantExpr(10))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + // !!!!!(5 == 10) => (5 != 10) + { + auto expr = pool.add( + notExpr(notExpr(notExpr(notExpr(notExpr(eqExpr(constantExpr(5), constantExpr(10)))))))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(neExpr(constantExpr(5), constantExpr(10))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } +} + +TEST_F(RewriteUnaryNotExprVisitorTest, TestMultipleUnaryNotExprLogicalRelExpr) { + // !!(5 == 10) AND !!(30 > 20) => (5 == 10) AND (30 > 20) + { + auto expr = pool.add(andExpr(notExpr(notExpr(eqExpr(constantExpr(5), constantExpr(10)))), + notExpr(notExpr(gtExpr(constantExpr(30), constantExpr(20)))))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(andExpr(eqExpr(constantExpr(5), constantExpr(10)), + gtExpr(constantExpr(30), constantExpr(20)))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + // !!!(5 <= 10) AND !(30 > 20) => (5 > 10) AND (30 <= 20) + { + auto expr = + pool.add(andExpr(notExpr(notExpr(notExpr(leExpr(constantExpr(5), constantExpr(10))))), + notExpr(gtExpr(constantExpr(30), constantExpr(20))))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(andExpr(gtExpr(constantExpr(5), constantExpr(10)), + leExpr(constantExpr(30), constantExpr(20)))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } +} + +TEST_F(RewriteUnaryNotExprVisitorTest, TestMultipleUnaryNotContainerExpr) { + // List + { + // [!!(5 == 10), !!!(30 > 20)] => [(5 == 10), (30 <= 20)] + auto expr = pool.add( + listExpr({notExpr(notExpr(eqExpr(constantExpr(5), constantExpr(10)))), + notExpr(notExpr(notExpr(gtExpr(constantExpr(30), constantExpr(20)))))})); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(listExpr({eqExpr(constantExpr(5), constantExpr(10)), + leExpr(constantExpr(30), constantExpr(20))})); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + // Set + { + // {!!(5 == 10), !!!(30 > 20)} => {(5 == 10), (30 <= 20)} + auto expr = pool.add( + setExpr({notExpr(notExpr(eqExpr(constantExpr(5), constantExpr(10)))), + notExpr(notExpr(notExpr(gtExpr(constantExpr(30), constantExpr(20)))))})); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(setExpr({eqExpr(constantExpr(5), constantExpr(10)), + leExpr(constantExpr(30), constantExpr(20))})); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + + // Map + { + // {"k1":!!(5 == 10), "k2":!!!(30 > 20)}} => {"k1":(5 == 10), "k2":(30 <= 20)} + auto expr = pool.add(mapExpr( + {{"k1", notExpr(notExpr(eqExpr(constantExpr(5), constantExpr(10))))}, + {"k2", notExpr(notExpr(notExpr(gtExpr(constantExpr(30), constantExpr(20)))))}})); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(mapExpr({{"k1", eqExpr(constantExpr(5), constantExpr(10))}, + {"k2", leExpr(constantExpr(30), constantExpr(20))}})); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } +} + +TEST_F(RewriteUnaryNotExprVisitorTest, TestRelExpr) { + // (5 == 10) => (5 == 10) + // no change should be made to the orginal expression + { + auto original = pool.add(eqExpr(constantExpr(5), constantExpr(10))); + RewriteUnaryNotExprVisitor visitor(&pool); + original->accept(&visitor); + auto res = visitor.getExpr(); + ASSERT_EQ(original, res) << original->toString() << " vs. " << res->toString(); + } + // !(5 == 10) => (5 != 10) + { + auto expr = pool.add(notExpr(eqExpr(constantExpr(5), constantExpr(10)))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(neExpr(constantExpr(5), constantExpr(10))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + // !(5 > 10) => (5 <= 10) + { + auto expr = pool.add(notExpr(gtExpr(constantExpr(5), constantExpr(10)))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(leExpr(constantExpr(5), constantExpr(10))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + // !(5 >= 10) => (5 < 10) + { + auto expr = pool.add(notExpr(geExpr(constantExpr(5), constantExpr(10)))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(ltExpr(constantExpr(5), constantExpr(10))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + // !("bcd" IN "abcde") => ("bcd" NOT IN "abcde") + { + auto expr = pool.add(notExpr(inExpr(constantExpr("bcd"), constantExpr("abcde")))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(notInExpr(constantExpr("bcd"), constantExpr("abcde"))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + + // !("bcd" NOT IN "abcde") => ("bcd" IN "abcde") + { + auto expr = pool.add(notExpr(notInExpr(constantExpr("bcd"), constantExpr("abcde")))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(inExpr(constantExpr("bcd"), constantExpr("abcde"))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + + // !("bcd" STARTS WITH "abc") => ("bcd" NOT STARTS WITH "abc") + { + auto expr = pool.add(notExpr(startsWithExpr(constantExpr("bcd"), constantExpr("abcde")))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(notStartsWithExpr(constantExpr("bcd"), constantExpr("abcde"))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } + + // !("bcd" ENDS WITH "abc") => ("bcd" NOT ENDS WITH "abc") + { + auto expr = pool.add(notExpr(endsWithExpr(constantExpr("bcd"), constantExpr("abcde")))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + auto expected = pool.add(notEndsWithExpr(constantExpr("bcd"), constantExpr("abcde"))); + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } +} + +TEST_F(RewriteUnaryNotExprVisitorTest, TestLogicalExpr) { + // !( 1 != 1 && 2 >= 3 && 30 <= 20) => (1 == 1 || 2 < 3 || 30 > 20) + { + auto expr = pool.add(notExpr(andExpr(andExpr(neExpr(constantExpr(1), constantExpr(1)), + geExpr(constantExpr(2), constantExpr(3))), + leExpr(constantExpr(30), constantExpr(20))))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + + LogicalExpression expected(Expression::Kind::kLogicalOr); + expected.addOperand(eqExpr(constantExpr(1), constantExpr(1))); + expected.addOperand(ltExpr(constantExpr(2), constantExpr(3))); + expected.addOperand(gtExpr(constantExpr(30), constantExpr(20))); + + ASSERT_EQ(*res, expected) << res->toString() << " vs. " << expected.toString(); + } + // !( 1 != 1 || 2 >= 3 || 30 <= 20) => (1 == 1 && 2 < 3 && 30 > 20) + { + auto expr = pool.add(notExpr(orExpr(orExpr(neExpr(constantExpr(1), constantExpr(1)), + geExpr(constantExpr(2), constantExpr(3))), + leExpr(constantExpr(30), constantExpr(20))))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + + LogicalExpression expected(Expression::Kind::kLogicalAnd); + expected.addOperand(eqExpr(constantExpr(1), constantExpr(1))); + expected.addOperand(ltExpr(constantExpr(2), constantExpr(3))); + expected.addOperand(gtExpr(constantExpr(30), constantExpr(20))); + + ASSERT_EQ(*res, expected) << res->toString() << " vs. " << expected.toString(); + } + // !( 1 != 1 && 2 >= 3 || 30 <= 20) => ((1 == 1 || 2 < 3) && 30 > 20) + { + auto expr = pool.add(notExpr(orExpr(andExpr(neExpr(constantExpr(1), constantExpr(1)), + geExpr(constantExpr(2), constantExpr(3))), + leExpr(constantExpr(30), constantExpr(20))))); + RewriteUnaryNotExprVisitor visitor(&pool); + expr->accept(&visitor); + auto res = visitor.getExpr(); + + auto expected = pool.add(andExpr(orExpr(eqExpr(constantExpr(1), constantExpr(1)), + ltExpr(constantExpr(2), constantExpr(3))), + gtExpr(constantExpr(30), constantExpr(20)))); + + ASSERT_EQ(*res, *expected) << res->toString() << " vs. " << expected->toString(); + } +} + +} // namespace graph +} // namespace nebula diff --git a/src/visitor/test/VisitorTestBase.h b/src/visitor/test/VisitorTestBase.h new file mode 100644 index 0000000000000000000000000000000000000000..8e514de69912a2380aafae149ae267bfa7d79e65 --- /dev/null +++ b/src/visitor/test/VisitorTestBase.h @@ -0,0 +1,163 @@ +/* Copyright (c) 2021 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. + */ + +#ifndef _VISITOR_TEST_VISITOR_TEST_BASE_H_ +#define _VISITOR_TEST_VISITOR_TEST_BASE_H_ + +#include <gtest/gtest.h> + +#include "common/expression/ExprVisitor.h" + +using Type = nebula::Value::Type; + +namespace nebula { +namespace graph { + +class ValidatorTestBase : public ::testing::Test { +protected: + static ConstantExpression *constantExpr(Value value) { + return new ConstantExpression(std::move(value)); + } + + static ArithmeticExpression *addExpr(Expression *lhs, Expression *rhs) { + return new ArithmeticExpression(Expression::Kind::kAdd, lhs, rhs); + } + + static ArithmeticExpression *minusExpr(Expression *lhs, Expression *rhs) { + return new ArithmeticExpression(Expression::Kind::kMinus, lhs, rhs); + } + + static RelationalExpression *eqExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kRelEQ, lhs, rhs); + } + + static RelationalExpression *neExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kRelNE, lhs, rhs); + } + + static RelationalExpression *ltExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kRelLT, lhs, rhs); + } + + static RelationalExpression *leExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kRelLE, lhs, rhs); + } + + static RelationalExpression *gtExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kRelGT, lhs, rhs); + } + + static RelationalExpression *geExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kRelGE, lhs, rhs); + } + + static RelationalExpression *inExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kRelIn, lhs, rhs); + } + + static RelationalExpression *notInExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kRelNotIn, lhs, rhs); + } + + static RelationalExpression *containsExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kContains, lhs, rhs); + } + + static RelationalExpression *notContainsExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kNotContains, lhs, rhs); + } + + static RelationalExpression *startsWithExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kStartsWith, lhs, rhs); + } + + static RelationalExpression *notStartsWithExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kNotStartsWith, lhs, rhs); + } + + static RelationalExpression *endsWithExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kEndsWith, lhs, rhs); + } + + static RelationalExpression *notEndsWithExpr(Expression *lhs, Expression *rhs) { + return new RelationalExpression(Expression::Kind::kNotEndsWith, lhs, rhs); + } + + static TypeCastingExpression *castExpr(Type type, Expression *expr) { + return new TypeCastingExpression(type, expr); + } + + static UnaryExpression *notExpr(Expression *expr) { + return new UnaryExpression(Expression::Kind::kUnaryNot, expr); + } + + static LogicalExpression *andExpr(Expression *lhs, Expression *rhs) { + return new LogicalExpression(Expression::Kind::kLogicalAnd, lhs, rhs); + } + + static LogicalExpression *orExpr(Expression *lhs, Expression *rhs) { + return new LogicalExpression(Expression::Kind::kLogicalOr, lhs, rhs); + } + + static ListExpression *listExpr(std::initializer_list<Expression *> exprs) { + auto exprList = new ExpressionList; + for (auto expr : exprs) { + exprList->add(expr); + } + return new ListExpression(exprList); + } + + static SetExpression *setExpr(std::initializer_list<Expression *> exprs) { + auto exprList = new ExpressionList; + for (auto expr : exprs) { + exprList->add(expr); + } + return new SetExpression(exprList); + } + + static MapExpression *mapExpr( + std::initializer_list<std::pair<std::string, Expression *>> exprs) { + auto mapItemList = new MapItemList; + for (auto expr : exprs) { + mapItemList->add(new std::string(expr.first), expr.second); + } + return new MapExpression(mapItemList); + } + + static SubscriptExpression *subExpr(Expression *lhs, Expression *rhs) { + return new SubscriptExpression(lhs, rhs); + } + + static FunctionCallExpression *fnExpr(std::string fn, + std::initializer_list<Expression *> args) { + auto argsList = new ArgumentList; + for (auto arg : args) { + argsList->addArgument(std::unique_ptr<Expression>(arg)); + } + return new FunctionCallExpression(new std::string(std::move(fn)), argsList); + } + + static VariableExpression *varExpr(const std::string &name) { + return new VariableExpression(new std::string(name)); + } + + static CaseExpression *caseExpr(Expression *cond, + Expression *defaltResult, + Expression *when, + Expression *then) { + auto caseList = new CaseList; + caseList->add(when, then); + auto expr = new CaseExpression(caseList); + expr->setCondition(cond); + expr->setDefault(defaltResult); + return expr; + } +}; + +} // namespace graph +} // namespace nebula + +#endif // VISITOR_TEST_VISITORTESTBASE_H_ diff --git a/tests/tck/features/expression/UnaryExpr.feature b/tests/tck/features/expression/UnaryExpr.feature index 021e157b7b3c0fe33dcbd2ae6a085ecf2dd2b560..ee3a47e274942a1264ef1a3682fd87a52824583f 100644 --- a/tests/tck/features/expression/UnaryExpr.feature +++ b/tests/tck/features/expression/UnaryExpr.feature @@ -76,3 +76,33 @@ Feature: UnaryExpression | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | | ("Vince Carter" :player{age: 42, name: "Vince Carter"}) | | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | + + Scenario: Unary deduce + When profiling query: + """ + MATCH (v:player) WHERE !!(v.age>=40) + RETURN v + """ + Then the result should be, in any order: + | v | + | ("Kobe Bryant" :player{age: 40, name: "Kobe Bryant"}) | + | ("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"}) | + | ("Ray Allen" :player{age: 43, name: "Ray Allen"}) | + | ("Dirk Nowitzki" :player{age: 40, name: "Dirk Nowitzki"}) | + | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | + | ("Vince Carter" :player{age: 42, name: "Vince Carter"}) | + | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | + | ("Grant Hill" :player{age: 46, name: "Grant Hill"}) | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | + And the execution plan should be: + | id | name | dependencies | operator info | + | 10 | Project | 9 | | + | 9 | Filter | 8 | | + | 8 | Filter | 7 | | + | 7 | Project | 6 | | + | 6 | Project | 5 | | + | 5 | Filter | 13 | | + | 13 | GetVertices | 11 | | + | 11 | IndexScan | 0 | {"indexCtx": {"columnHints":{"scanType":"RANGE"}}} | + | 0 | Start | | |