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       |              |                                                    |