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