From 643e177a552fa4e0780c0ba2582fb474103b2369 Mon Sep 17 00:00:00 2001
From: jimingquan <mingquan.ji@vesoft.com>
Date: Thu, 18 Mar 2021 15:56:45 +0800
Subject: [PATCH] add exists (#808)

* add exists

* fix error

* rewite exists

* add test case

* more test case

* rebase & remove header

Co-authored-by: cpw <13495049+CPWstatic@users.noreply.github.com>
---
 src/parser/parser.yy                         |   8 +-
 src/planner/SequentialPlanner.cpp            |   5 +-
 src/planner/match/MatchPlanner.cpp           |   6 +-
 src/util/CMakeLists.txt                      |   1 +
 src/util/ParserUtil.cpp                      | 176 +++++++++++++++++++
 src/util/ParserUtil.h                        | 159 +----------------
 src/validator/Validator.cpp                  |   3 +-
 src/visitor/CollectAllExprsVisitor.cpp       |   4 +-
 src/visitor/DeduceTypeVisitor.cpp            |   8 +-
 src/visitor/ExprVisitorImpl.cpp              |   8 +-
 src/visitor/FindAnyExprVisitor.cpp           |   4 +-
 src/visitor/FoldConstantExprVisitor.cpp      |  14 +-
 src/visitor/RewriteInputPropVisitor.cpp      |   8 +-
 src/visitor/RewriteLabelAttrVisitor.cpp      |  12 +-
 src/visitor/RewriteMatchLabelVisitor.cpp     |  10 +-
 src/visitor/RewriteSymExprVisitor.cpp        |   8 +-
 tests/tck/features/match/Base.IntVid.feature |  51 ++++++
 tests/tck/features/match/Base.feature        |  51 ++++++
 tests/tck/features/match/WithUnwind.feature  |  34 ++++
 19 files changed, 376 insertions(+), 194 deletions(-)
 create mode 100644 src/util/ParserUtil.cpp

diff --git a/src/parser/parser.yy b/src/parser/parser.yy
index b42548d1..dd02a1c9 100644
--- a/src/parser/parser.yy
+++ b/src/parser/parser.yy
@@ -31,8 +31,6 @@
 #include "util/ParserUtil.h"
 #include "context/QueryContext.h"
 #include "util/SchemaUtil.h"
-#include "util/ParserUtil.h"
-#include "context/QueryContext.h"
 
 namespace nebula {
 
@@ -801,6 +799,12 @@ predicate_expression
         $$ = expr;
         delete $3;
     }
+    | KW_EXISTS L_PAREN expression R_PAREN {
+        if ($3->kind() != Expression::Kind::kLabelAttribute && $3->kind() != Expression::Kind::kAttribute) {
+            throw nebula::GraphParser::syntax_error(@3, "The exists only accept LabelAttribe OR Attribute");
+        }
+        $$ = new PredicateExpression(new std::string("exists"), nullptr, $3, nullptr);
+    }
     ;
 
 list_comprehension_expression
diff --git a/src/planner/SequentialPlanner.cpp b/src/planner/SequentialPlanner.cpp
index 66827d82..ec7fd1d9 100644
--- a/src/planner/SequentialPlanner.cpp
+++ b/src/planner/SequentialPlanner.cpp
@@ -14,10 +14,7 @@
 namespace nebula {
 namespace graph {
 bool SequentialPlanner::match(AstContext* astCtx) {
-    if (astCtx->sentence->kind() == Sentence::Kind::kSequential) {
-        return true;
-    }
-    return false;
+    return astCtx->sentence->kind() == Sentence::Kind::kSequential;
 }
 
 StatusOr<SubPlan> SequentialPlanner::transform(AstContext* astCtx) {
diff --git a/src/planner/match/MatchPlanner.cpp b/src/planner/match/MatchPlanner.cpp
index 2743e2da..1538fbeb 100644
--- a/src/planner/match/MatchPlanner.cpp
+++ b/src/planner/match/MatchPlanner.cpp
@@ -16,11 +16,7 @@
 namespace nebula {
 namespace graph {
 bool MatchPlanner::match(AstContext* astCtx) {
-    if (astCtx->sentence->kind() == Sentence::Kind::kMatch) {
-        return true;
-    } else {
-        return false;
-    }
+    return astCtx->sentence->kind() == Sentence::Kind::kMatch;
 }
 
 StatusOr<SubPlan> MatchPlanner::transform(AstContext* astCtx) {
diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt
index 0cb2e6c6..e13fc6e5 100644
--- a/src/util/CMakeLists.txt
+++ b/src/util/CMakeLists.txt
@@ -11,6 +11,7 @@ nebula_add_library(
     IndexUtil.cpp
     ZoneUtil.cpp
     ToJson.cpp
+    ParserUtil.cpp
 )
 
 nebula_add_library(
diff --git a/src/util/ParserUtil.cpp b/src/util/ParserUtil.cpp
new file mode 100644
index 00000000..164ebc85
--- /dev/null
+++ b/src/util/ParserUtil.cpp
@@ -0,0 +1,176 @@
+/* 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 "util/ParserUtil.h"
+#include "common/base/Status.h"
+#include "common/base/StatusOr.h"
+
+namespace nebula {
+namespace graph {
+
+// static
+bool ParserUtil::isLabel(const Expression *expr) {
+    return expr->kind() == Expression::Kind::kLabel ||
+           expr->kind() == Expression::Kind::kLabelAttribute;
+}
+
+// static
+void ParserUtil::rewriteLC(QueryContext *qctx,
+                           ListComprehensionExpression *lc,
+                           const std::string &oldVarName) {
+    const auto &newVarName = qctx->vctx()->anonVarGen()->getVar();
+    qctx->ectx()->setValue(newVarName, Value());
+    auto rewriter = [&oldVarName, &newVarName](const Expression *expr) {
+        Expression *ret = nullptr;
+        if (expr->kind() == Expression::Kind::kLabel) {
+            auto *label = static_cast<const LabelExpression *>(expr);
+            if (*label->name() == oldVarName) {
+                ret = new VariableExpression(new std::string(newVarName));
+            } else {
+                ret = label->clone().release();
+            }
+        } else {
+            DCHECK(expr->kind() == Expression::Kind::kLabelAttribute);
+            auto *la = static_cast<const LabelAttributeExpression *>(expr);
+            if (*la->left()->name() == oldVarName) {
+                const auto &value = la->right()->value();
+                ret = new AttributeExpression(new VariableExpression(new std::string(newVarName)),
+                                              new ConstantExpression(value));
+            } else {
+                ret = la->clone().release();
+            }
+        }
+        return ret;
+    };
+
+    RewriteMatchLabelVisitor visitor(rewriter);
+
+    lc->setOriginString(new std::string(lc->makeString()));
+    lc->setInnerVar(new std::string(newVarName));
+    if (lc->hasFilter()) {
+        Expression *filter = lc->filter();
+        Expression *newFilter = nullptr;
+        if (isLabel(filter)) {
+            newFilter = rewriter(filter);
+        } else {
+            newFilter = filter->clone().release();
+            newFilter->accept(&visitor);
+        }
+        lc->setFilter(newFilter);
+    }
+    if (lc->hasMapping()) {
+        Expression *mapping = lc->mapping();
+        Expression *newMapping = nullptr;
+        if (isLabel(mapping)) {
+            newMapping = rewriter(mapping);
+        } else {
+            newMapping = mapping->clone().release();
+            newMapping->accept(&visitor);
+        }
+        lc->setMapping(newMapping);
+    }
+}
+
+// static
+void ParserUtil::rewritePred(QueryContext *qctx,
+                             PredicateExpression *pred,
+                             const std::string &oldVarName) {
+    const auto &newVarName = qctx->vctx()->anonVarGen()->getVar();
+    qctx->ectx()->setValue(newVarName, Value());
+    auto rewriter = [&oldVarName, &newVarName](const Expression *expr) {
+        Expression *ret = nullptr;
+        if (expr->kind() == Expression::Kind::kLabel) {
+            auto *label = static_cast<const LabelExpression *>(expr);
+            if (*label->name() == oldVarName) {
+                ret = new VariableExpression(new std::string(newVarName));
+            } else {
+                ret = label->clone().release();
+            }
+        } else {
+            DCHECK(expr->kind() == Expression::Kind::kLabelAttribute);
+            auto *la = static_cast<const LabelAttributeExpression *>(expr);
+            if (*la->left()->name() == oldVarName) {
+                const auto &value = la->right()->value();
+                ret = new AttributeExpression(new VariableExpression(new std::string(newVarName)),
+                                              new ConstantExpression(value));
+            } else {
+                ret = la->clone().release();
+            }
+        }
+        return ret;
+    };
+
+    RewriteMatchLabelVisitor visitor(rewriter);
+
+    pred->setOriginString(new std::string(pred->makeString()));
+    pred->setInnerVar(new std::string(newVarName));
+    Expression *filter = pred->filter();
+    Expression *newFilter = nullptr;
+    if (isLabel(filter)) {
+        newFilter = rewriter(filter);
+    } else {
+        newFilter = filter->clone().release();
+        newFilter->accept(&visitor);
+    }
+    pred->setFilter(newFilter);
+}
+
+// static
+void ParserUtil::rewriteReduce(QueryContext *qctx,
+                               ReduceExpression *reduce,
+                               const std::string &oldAccName,
+                               const std::string &oldVarName) {
+    const auto &newAccName = qctx->vctx()->anonVarGen()->getVar();
+    qctx->ectx()->setValue(newAccName, Value());
+    const auto &newVarName = qctx->vctx()->anonVarGen()->getVar();
+    qctx->ectx()->setValue(newVarName, Value());
+    auto rewriter = [oldAccName, newAccName, oldVarName, newVarName](const Expression *expr) {
+        Expression *ret = nullptr;
+        if (expr->kind() == Expression::Kind::kLabel) {
+            auto *label = static_cast<const LabelExpression *>(expr);
+            if (*label->name() == oldAccName) {
+                ret = new VariableExpression(new std::string(newAccName));
+            } else if (*label->name() == oldVarName) {
+                ret = new VariableExpression(new std::string(newVarName));
+            } else {
+                ret = label->clone().release();
+            }
+        } else {
+            DCHECK(expr->kind() == Expression::Kind::kLabelAttribute);
+            auto *la = static_cast<const LabelAttributeExpression *>(expr);
+            if (*la->left()->name() == oldAccName) {
+                const auto &value = la->right()->value();
+                ret = new AttributeExpression(new VariableExpression(new std::string(newAccName)),
+                                              new ConstantExpression(value));
+            } else if (*la->left()->name() == oldVarName) {
+                const auto &value = la->right()->value();
+                ret = new AttributeExpression(new VariableExpression(new std::string(newVarName)),
+                                              new ConstantExpression(value));
+            } else {
+                ret = la->clone().release();
+            }
+        }
+        return ret;
+    };
+
+    RewriteMatchLabelVisitor visitor(rewriter);
+
+    reduce->setOriginString(new std::string(reduce->makeString()));
+    reduce->setAccumulator(new std::string(newAccName));
+    reduce->setInnerVar(new std::string(newVarName));
+    Expression *mapping = reduce->mapping();
+    Expression *newMapping = nullptr;
+    if (isLabel(mapping)) {
+        newMapping = rewriter(mapping);
+    } else {
+        newMapping = mapping->clone().release();
+        newMapping->accept(&visitor);
+    }
+    reduce->setMapping(newMapping);
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/util/ParserUtil.h b/src/util/ParserUtil.h
index e64a20a9..16670479 100644
--- a/src/util/ParserUtil.h
+++ b/src/util/ParserUtil.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.
@@ -20,168 +20,19 @@ class ParserUtil final {
 public:
     ParserUtil() = delete;
 
-    static bool isLabel(const Expression *expr) {
-        return expr->kind() == Expression::Kind::kLabel ||
-               expr->kind() == Expression::Kind::kLabelAttribute;
-    }
-
+    static bool isLabel(const Expression *expr);
     static void rewriteLC(QueryContext *qctx,
                           ListComprehensionExpression *lc,
-                          const std::string &oldVarName) {
-        const auto &newVarName = qctx->vctx()->anonVarGen()->getVar();
-        qctx->ectx()->setValue(newVarName, Value());
-        auto rewriter = [&oldVarName, &newVarName](const Expression *expr) {
-            Expression *ret = nullptr;
-            if (expr->kind() == Expression::Kind::kLabel) {
-                auto *label = static_cast<const LabelExpression *>(expr);
-                if (*label->name() == oldVarName) {
-                    ret = new VariableExpression(new std::string(newVarName));
-                } else {
-                    ret = label->clone().release();
-                }
-            } else {
-                DCHECK(expr->kind() == Expression::Kind::kLabelAttribute);
-                auto *la = static_cast<const LabelAttributeExpression *>(expr);
-                if (*la->left()->name() == oldVarName) {
-                    const auto &value = la->right()->value();
-                    ret =
-                        new AttributeExpression(new VariableExpression(new std::string(newVarName)),
-                                                new ConstantExpression(value));
-                } else {
-                    ret = la->clone().release();
-                }
-            }
-            return ret;
-        };
-
-        RewriteMatchLabelVisitor visitor(rewriter);
-
-        lc->setOriginString(new std::string(lc->makeString()));
-        lc->setInnerVar(new std::string(newVarName));
-        if (lc->hasFilter()) {
-            Expression *filter = lc->filter();
-            Expression *newFilter = nullptr;
-            if (isLabel(filter)) {
-                newFilter = rewriter(filter);
-            } else {
-                newFilter = filter->clone().release();
-                newFilter->accept(&visitor);
-            }
-            lc->setFilter(newFilter);
-        }
-        if (lc->hasMapping()) {
-            Expression *mapping = lc->mapping();
-            Expression *newMapping = nullptr;
-            if (isLabel(mapping)) {
-                newMapping = rewriter(mapping);
-            } else {
-                newMapping = mapping->clone().release();
-                newMapping->accept(&visitor);
-            }
-            lc->setMapping(newMapping);
-        }
-    }
+                          const std::string &oldVarName);
 
     static void rewritePred(QueryContext *qctx,
                             PredicateExpression *pred,
-                            const std::string &oldVarName) {
-        const auto &newVarName = qctx->vctx()->anonVarGen()->getVar();
-        qctx->ectx()->setValue(newVarName, Value());
-        auto rewriter = [&oldVarName, &newVarName](const Expression *expr) {
-            Expression *ret = nullptr;
-            if (expr->kind() == Expression::Kind::kLabel) {
-                auto *label = static_cast<const LabelExpression *>(expr);
-                if (*label->name() == oldVarName) {
-                    ret = new VariableExpression(new std::string(newVarName));
-                } else {
-                    ret = label->clone().release();
-                }
-            } else {
-                DCHECK(expr->kind() == Expression::Kind::kLabelAttribute);
-                auto *la = static_cast<const LabelAttributeExpression *>(expr);
-                if (*la->left()->name() == oldVarName) {
-                    const auto &value = la->right()->value();
-                    ret =
-                        new AttributeExpression(new VariableExpression(new std::string(newVarName)),
-                                                new ConstantExpression(value));
-                } else {
-                    ret = la->clone().release();
-                }
-            }
-            return ret;
-        };
-
-        RewriteMatchLabelVisitor visitor(rewriter);
-
-        pred->setOriginString(new std::string(pred->makeString()));
-        pred->setInnerVar(new std::string(newVarName));
-        Expression *filter = pred->filter();
-        Expression *newFilter = nullptr;
-        if (isLabel(filter)) {
-            newFilter = rewriter(filter);
-        } else {
-            newFilter = filter->clone().release();
-            newFilter->accept(&visitor);
-        }
-        pred->setFilter(newFilter);
-    }
-
+                            const std::string &oldVarName);
 
     static void rewriteReduce(QueryContext *qctx,
                               ReduceExpression *reduce,
                               const std::string &oldAccName,
-                              const std::string &oldVarName) {
-        const auto &newAccName = qctx->vctx()->anonVarGen()->getVar();
-        qctx->ectx()->setValue(newAccName, Value());
-        const auto &newVarName = qctx->vctx()->anonVarGen()->getVar();
-        qctx->ectx()->setValue(newVarName, Value());
-        auto rewriter = [oldAccName, newAccName, oldVarName, newVarName](
-                            const Expression *expr) {
-            Expression *ret = nullptr;
-            if (expr->kind() == Expression::Kind::kLabel) {
-                auto *label = static_cast<const LabelExpression *>(expr);
-                if (*label->name() == oldAccName) {
-                    ret = new VariableExpression(new std::string(newAccName));
-                } else if (*label->name() == oldVarName) {
-                    ret = new VariableExpression(new std::string(newVarName));
-                } else {
-                    ret = label->clone().release();
-                }
-            } else {
-                DCHECK(expr->kind() == Expression::Kind::kLabelAttribute);
-                auto *la = static_cast<const LabelAttributeExpression *>(expr);
-                if (*la->left()->name() == oldAccName) {
-                    const auto &value = la->right()->value();
-                    ret =
-                        new AttributeExpression(new VariableExpression(new std::string(newAccName)),
-                                                new ConstantExpression(value));
-                } else if (*la->left()->name() == oldVarName) {
-                    const auto &value = la->right()->value();
-                    ret =
-                        new AttributeExpression(new VariableExpression(new std::string(newVarName)),
-                                                new ConstantExpression(value));
-                } else {
-                    ret = la->clone().release();
-                }
-            }
-            return ret;
-        };
-
-        RewriteMatchLabelVisitor visitor(rewriter);
-
-        reduce->setOriginString(new std::string(reduce->makeString()));
-        reduce->setAccumulator(new std::string(newAccName));
-        reduce->setInnerVar(new std::string(newVarName));
-        Expression *mapping = reduce->mapping();
-        Expression *newMapping = nullptr;
-        if (isLabel(mapping)) {
-            newMapping = rewriter(mapping);
-        } else {
-            newMapping = mapping->clone().release();
-            newMapping->accept(&visitor);
-        }
-        reduce->setMapping(newMapping);
-    }
+                              const std::string &oldVarName);
 };
 
 }   // namespace graph
diff --git a/src/validator/Validator.cpp b/src/validator/Validator.cpp
index edad9953..818b6b67 100644
--- a/src/validator/Validator.cpp
+++ b/src/validator/Validator.cpp
@@ -343,9 +343,8 @@ std::vector<std::string> Validator::deduceColNames(const YieldColumns* cols) con
 std::string Validator::deduceColName(const YieldColumn* col) const {
     if (col->alias() != nullptr) {
         return *col->alias();
-    } else {
-        return col->toString();
     }
+    return col->toString();
 }
 
 StatusOr<Value::Type> Validator::deduceExprType(const Expression* expr) const {
diff --git a/src/visitor/CollectAllExprsVisitor.cpp b/src/visitor/CollectAllExprsVisitor.cpp
index 00ea67a0..4034e871 100644
--- a/src/visitor/CollectAllExprsVisitor.cpp
+++ b/src/visitor/CollectAllExprsVisitor.cpp
@@ -160,7 +160,9 @@ void CollectAllExprsVisitor::visit(ListComprehensionExpression* expr) {
 void CollectAllExprsVisitor::visit(PredicateExpression *expr) {
     collectExpr(expr);
     expr->collection()->accept(this);
-    expr->filter()->accept(this);
+    if (expr->hasFilter()) {
+        expr->filter()->accept(this);
+    }
 }
 
 void CollectAllExprsVisitor::visit(ReduceExpression *expr) {
diff --git a/src/visitor/DeduceTypeVisitor.cpp b/src/visitor/DeduceTypeVisitor.cpp
index 14f87e28..674b74d8 100644
--- a/src/visitor/DeduceTypeVisitor.cpp
+++ b/src/visitor/DeduceTypeVisitor.cpp
@@ -619,8 +619,12 @@ void DeduceTypeVisitor::visit(CaseExpression *expr) {
 }
 
 void DeduceTypeVisitor::visit(PredicateExpression *expr) {
-    expr->filter()->accept(this);
-    if (!ok()) return;
+    if (expr->hasFilter()) {
+        expr->filter()->accept(this);
+        if (!ok()) {
+            return;
+        }
+    }
 
     expr->collection()->accept(this);
     if (!ok()) return;
diff --git a/src/visitor/ExprVisitorImpl.cpp b/src/visitor/ExprVisitorImpl.cpp
index af1e469f..90974e07 100644
--- a/src/visitor/ExprVisitorImpl.cpp
+++ b/src/visitor/ExprVisitorImpl.cpp
@@ -149,8 +149,12 @@ void ExprVisitorImpl::visit(PredicateExpression *expr) {
     DCHECK(ok());
     expr->collection()->accept(this);
     if (!ok()) return;
-    expr->filter()->accept(this);
-    if (!ok()) return;
+    if (expr->hasFilter()) {
+        expr->filter()->accept(this);
+        if (!ok()) {
+            return;
+        }
+    }
 }
 
 void ExprVisitorImpl::visit(ListComprehensionExpression *expr) {
diff --git a/src/visitor/FindAnyExprVisitor.cpp b/src/visitor/FindAnyExprVisitor.cpp
index 8b108196..d45347b6 100644
--- a/src/visitor/FindAnyExprVisitor.cpp
+++ b/src/visitor/FindAnyExprVisitor.cpp
@@ -92,7 +92,9 @@ void FindAnyExprVisitor::visit(PredicateExpression *expr) {
     if (found_) return;
     expr->collection()->accept(this);
     if (found_) return;
-    expr->filter()->accept(this);
+    if (expr->hasFilter()) {
+        expr->filter()->accept(this);
+    }
 }
 
 void FindAnyExprVisitor::visit(ReduceExpression *expr) {
diff --git a/src/visitor/FoldConstantExprVisitor.cpp b/src/visitor/FoldConstantExprVisitor.cpp
index d0725f32..cb14830a 100644
--- a/src/visitor/FoldConstantExprVisitor.cpp
+++ b/src/visitor/FoldConstantExprVisitor.cpp
@@ -391,12 +391,14 @@ void FoldConstantExprVisitor::visit(PredicateExpression *expr) {
             canBeFolded = false;
         }
     }
-    if (!isConstant(expr->filter())) {
-        expr->filter()->accept(this);
-        if (canBeFolded_) {
-            expr->setFilter(fold(expr->filter()));
-        } else {
-            canBeFolded = false;
+    if (expr->hasFilter()) {
+        if (!isConstant(expr->filter())) {
+            expr->filter()->accept(this);
+            if (canBeFolded_) {
+                expr->setFilter(fold(expr->filter()));
+            } else {
+                canBeFolded = false;
+            }
         }
     }
     canBeFolded_ = canBeFolded;
diff --git a/src/visitor/RewriteInputPropVisitor.cpp b/src/visitor/RewriteInputPropVisitor.cpp
index c41073cb..9863f070 100644
--- a/src/visitor/RewriteInputPropVisitor.cpp
+++ b/src/visitor/RewriteInputPropVisitor.cpp
@@ -279,9 +279,11 @@ void RewriteInputPropVisitor::visit(PredicateExpression* expr) {
     if (ok()) {
         expr->setCollection(result_.release());
     }
-    expr->filter()->accept(this);
-    if (ok()) {
-        expr->setFilter(result_.release());
+    if (expr->hasFilter()) {
+        expr->filter()->accept(this);
+        if (ok()) {
+            expr->setFilter(result_.release());
+        }
     }
 }
 
diff --git a/src/visitor/RewriteLabelAttrVisitor.cpp b/src/visitor/RewriteLabelAttrVisitor.cpp
index 9e16e425..dfd8eb9f 100644
--- a/src/visitor/RewriteLabelAttrVisitor.cpp
+++ b/src/visitor/RewriteLabelAttrVisitor.cpp
@@ -152,11 +152,13 @@ void RewriteLabelAttrVisitor::visit(PredicateExpression* expr) {
     } else {
         expr->collection()->accept(this);
     }
-    if (isLabelAttrExpr(expr->filter())) {
-        auto newExpr = static_cast<LabelAttributeExpression*>(expr->filter());
-        expr->setFilter(createExpr(newExpr));
-    } else {
-        expr->filter()->accept(this);
+    if (expr->hasFilter()) {
+        if (isLabelAttrExpr(expr->filter())) {
+            auto newExpr = static_cast<LabelAttributeExpression*>(expr->filter());
+            expr->setFilter(createExpr(newExpr));
+        } else {
+            expr->filter()->accept(this);
+        }
     }
 }
 
diff --git a/src/visitor/RewriteMatchLabelVisitor.cpp b/src/visitor/RewriteMatchLabelVisitor.cpp
index 763303b1..de6787f5 100644
--- a/src/visitor/RewriteMatchLabelVisitor.cpp
+++ b/src/visitor/RewriteMatchLabelVisitor.cpp
@@ -157,10 +157,12 @@ void RewriteMatchLabelVisitor::visit(PredicateExpression *expr) {
     } else {
         expr->collection()->accept(this);
     }
-    if (isLabel(expr->filter())) {
-        expr->setFilter(rewriter_(expr->filter()));
-    } else {
-        expr->filter()->accept(this);
+    if (expr->hasFilter()) {
+        if (isLabel(expr->filter())) {
+            expr->setFilter(rewriter_(expr->filter()));
+        } else {
+            expr->filter()->accept(this);
+        }
     }
 }
 
diff --git a/src/visitor/RewriteSymExprVisitor.cpp b/src/visitor/RewriteSymExprVisitor.cpp
index ac05a3d3..87fcd271 100644
--- a/src/visitor/RewriteSymExprVisitor.cpp
+++ b/src/visitor/RewriteSymExprVisitor.cpp
@@ -292,9 +292,11 @@ void RewriteSymExprVisitor::visit(PredicateExpression *expr) {
     if (expr_) {
         expr->setCollection(expr_.release());
     }
-    expr->filter()->accept(this);
-    if (expr_) {
-        expr->setFilter(expr_.release());
+    if (expr->hasFilter()) {
+        expr->filter()->accept(this);
+        if (expr_) {
+            expr->setFilter(expr_.release());
+        }
     }
 }
 
diff --git a/tests/tck/features/match/Base.IntVid.feature b/tests/tck/features/match/Base.IntVid.feature
index 050de74c..999c9681 100644
--- a/tests/tck/features/match/Base.IntVid.feature
+++ b/tests/tck/features/match/Base.IntVid.feature
@@ -355,6 +355,57 @@ Feature: Basic match
       | p                                                                      |
       | <("LeBron James")-[:like@0]->("Ray Allen")-[:like@0]->("Rajon Rondo")> |
 
+  Scenario: exists
+    When executing query:
+      """
+      MATCH (:player{name:"Tony Parker"})-[r]->() where exists(r.likeness) return r, exists({a:12}.a)
+      """
+    Then the result should be, in any order, with relax comparison:
+      | r                                                            | exists({a:12}.a) |
+      | [:like "Tony Parker"->"LaMarcus Aldridge" @0 {likeness: 90}] | true             |
+      | [:like "Tony Parker"->"Manu Ginobili" @0 {likeness: 95}]     | true             |
+      | [:like "Tony Parker"->"Tim Duncan" @0 {likeness: 95}]        | true             |
+    When executing query:
+      """
+      MATCH (:player{name:"Tony Parker"})-[r]->(m) where exists(m.likeness) return r, exists({a:12}.a)
+      """
+    Then the result should be, in any order, with relax comparison:
+      | r | exists({a:12}.a) |
+    When executing query:
+      """
+      match (:player{name:"Tony Parker"})-[r]->(m) where exists({abc:123}.abc) return r
+      """
+    Then the result should be, in any order, with relax comparison:
+      | r                                                                                    |
+      | [:teammate "Tony Parker"->"Kyle Anderson" @0 {end_year: 2016, start_year: 2014}]     |
+      | [:teammate "Tony Parker"->"LaMarcus Aldridge" @0 {end_year: 2018, start_year: 2015}] |
+      | [:teammate "Tony Parker"->"Manu Ginobili" @0 {end_year: 2018, start_year: 2002}]     |
+      | [:teammate "Tony Parker"->"Tim Duncan" @0 {end_year: 2016, start_year: 2001}]        |
+      | [:like "Tony Parker"->"LaMarcus Aldridge" @0 {likeness: 90}]                         |
+      | [:like "Tony Parker"->"Manu Ginobili" @0 {likeness: 95}]                             |
+      | [:like "Tony Parker"->"Tim Duncan" @0 {likeness: 95}]                                |
+      | [:serve "Tony Parker"->"Hornets" @0 {end_year: 2019, start_year: 2018}]              |
+      | [:serve "Tony Parker"->"Spurs" @0 {end_year: 2018, start_year: 1999}]                |
+    When executing query:
+      """
+      match (:player{name:"Tony Parker"})-[r]->(m) where exists({abc:123}.ab) return r
+      """
+    Then the result should be, in any order, with relax comparison:
+      | r |
+    When executing query:
+      """
+      match (:player{name:"Tony Parker"})-[r]->(m) where exists(m.age) return r
+      """
+    Then the result should be, in any order, with relax comparison:
+      | r                                                                                    |
+      | [:teammate "Tony Parker"->"Kyle Anderson" @0 {end_year: 2016, start_year: 2014}]     |
+      | [:teammate "Tony Parker"->"LaMarcus Aldridge" @0 {end_year: 2018, start_year: 2015}] |
+      | [:teammate "Tony Parker"->"Manu Ginobili" @0 {end_year: 2018, start_year: 2002}]     |
+      | [:teammate "Tony Parker"->"Tim Duncan" @0 {end_year: 2016, start_year: 2001}]        |
+      | [:like "Tony Parker"->"LaMarcus Aldridge" @0 {likeness: 90}]                         |
+      | [:like "Tony Parker"->"Manu Ginobili" @0 {likeness: 95}]                             |
+      | [:like "Tony Parker"->"Tim Duncan" @0 {likeness: 95}]                                |
+
   Scenario: No return
     When executing query:
       """
diff --git a/tests/tck/features/match/Base.feature b/tests/tck/features/match/Base.feature
index 9b2705ba..5c11ece8 100644
--- a/tests/tck/features/match/Base.feature
+++ b/tests/tck/features/match/Base.feature
@@ -382,6 +382,57 @@ Feature: Basic match
       """
     Then a SemanticError should be raised at runtime: Match clause is not supported to be followed by other cypher clauses
 
+  Scenario: exists
+    When executing query:
+      """
+      match (:player{name:"Tony Parker"})-[r]->() where exists(r.likeness) return r, exists({a:12}.a)
+      """
+    Then the result should be, in any order, with relax comparison:
+      | r                                                            | exists({a:12}.a) |
+      | [:like "Tony Parker"->"LaMarcus Aldridge" @0 {likeness: 90}] | true             |
+      | [:like "Tony Parker"->"Manu Ginobili" @0 {likeness: 95}]     | true             |
+      | [:like "Tony Parker"->"Tim Duncan" @0 {likeness: 95}]        | true             |
+    When executing query:
+      """
+      match (:player{name:"Tony Parker"})-[r]->(m) where exists(m.likeness) return r, exists({a:12}.a)
+      """
+    Then the result should be, in any order, with relax comparison:
+      | r | exists({a:12}.a) |
+    When executing query:
+      """
+      match (:player{name:"Tony Parker"})-[r]->(m) where exists({abc:123}.abc) return r
+      """
+    Then the result should be, in any order, with relax comparison:
+      | r                                                                                    |
+      | [:teammate "Tony Parker"->"Kyle Anderson" @0 {end_year: 2016, start_year: 2014}]     |
+      | [:teammate "Tony Parker"->"LaMarcus Aldridge" @0 {end_year: 2018, start_year: 2015}] |
+      | [:teammate "Tony Parker"->"Manu Ginobili" @0 {end_year: 2018, start_year: 2002}]     |
+      | [:teammate "Tony Parker"->"Tim Duncan" @0 {end_year: 2016, start_year: 2001}]        |
+      | [:like "Tony Parker"->"LaMarcus Aldridge" @0 {likeness: 90}]                         |
+      | [:like "Tony Parker"->"Manu Ginobili" @0 {likeness: 95}]                             |
+      | [:like "Tony Parker"->"Tim Duncan" @0 {likeness: 95}]                                |
+      | [:serve "Tony Parker"->"Hornets" @0 {end_year: 2019, start_year: 2018}]              |
+      | [:serve "Tony Parker"->"Spurs" @0 {end_year: 2018, start_year: 1999}]                |
+    When executing query:
+      """
+      match (:player{name:"Tony Parker"})-[r]->(m) where exists({abc:123}.ab) return r
+      """
+    Then the result should be, in any order, with relax comparison:
+      | r |
+    When executing query:
+      """
+      match (:player{name:"Tony Parker"})-[r]->(m) where exists(m.age) return r
+      """
+    Then the result should be, in any order, with relax comparison:
+      | r                                                                                    |
+      | [:teammate "Tony Parker"->"Kyle Anderson" @0 {end_year: 2016, start_year: 2014}]     |
+      | [:teammate "Tony Parker"->"LaMarcus Aldridge" @0 {end_year: 2018, start_year: 2015}] |
+      | [:teammate "Tony Parker"->"Manu Ginobili" @0 {end_year: 2018, start_year: 2002}]     |
+      | [:teammate "Tony Parker"->"Tim Duncan" @0 {end_year: 2016, start_year: 2001}]        |
+      | [:like "Tony Parker"->"LaMarcus Aldridge" @0 {likeness: 90}]                         |
+      | [:like "Tony Parker"->"Manu Ginobili" @0 {likeness: 95}]                             |
+      | [:like "Tony Parker"->"Tim Duncan" @0 {likeness: 95}]                                |
+
   Scenario: No return
     When executing query:
       """
diff --git a/tests/tck/features/match/WithUnwind.feature b/tests/tck/features/match/WithUnwind.feature
index f1f15378..4fb1b9c5 100644
--- a/tests/tck/features/match/WithUnwind.feature
+++ b/tests/tck/features/match/WithUnwind.feature
@@ -267,3 +267,37 @@ Feature: With clause and Unwind clause
       | ("Tony Parker" :player{age: 36, name: "Tony Parker"})                                                       | 36  |
       | ("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})                                               | 34  |
       | ("LeBron James" :player{age: 34, name: "LeBron James"})                                                     | 34  |
+
+  Scenario: with exists
+    When executing query:
+      """
+      WITH {abc:123} AS m, "hello" AS b
+      RETURN exists(m.abc), b
+      """
+    Then the result should be, in any order, with relax comparison:
+      | exists(m.abc) | b       |
+      | true          | "hello" |
+    When executing query:
+      """
+      WITH {abc:123} AS m, "hello" AS b
+      RETURN exists(m.abc), exists(m.a), exists({label:"hello"}.label) as t, exists({hello:123}.a)
+      """
+    Then the result should be, in any order, with relax comparison:
+      | exists(m.abc) | exists(m.a) | t    | exists({hello:123}.a) |
+      | true          | false       | true | false                 |
+    When executing query:
+      """
+      WITH [1,2,3] AS m
+      RETURN exists(m.abc)
+      """
+    Then the result should be, in any order, with relax comparison:
+      | exists(m.abc) |
+      | BAD_TYPE      |
+    When executing query:
+      """
+      WITH null AS m
+      RETURN exists(m.abc), exists((null).abc)
+      """
+    Then the result should be, in any order, with relax comparison:
+      | exists(m.abc) | exists(NULL.abc) |
+      | NULL          | NULL             |
-- 
GitLab