diff --git a/src/parser/parser.yy b/src/parser/parser.yy index b42548d16d5800a576d7cff8c48a62f9d4395bcc..dd02a1c9e5935b0d02dbbe988b6e9f1dd1b91664 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 66827d82266f2b506390401e26eae973e0611174..ec7fd1d912abc9bbdc4a391470a95acf506b6279 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 2743e2da858f49bd318b8951b0310659e5988653..1538fbeb8efa7ddd58165087ed08d42d8f8462a8 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 0cb2e6c6640445b7af4d9460954e98990c46f12b..e13fc6e539453aada48c0ef202dc20dfc7a14485 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 0000000000000000000000000000000000000000..164ebc8542c220dafb3aa05d2cf34d9bf41be9d8 --- /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 e64a20a9a04fed587f8a39cc5df5fcd1da6b179a..16670479a7bcf81967abfd4063c9fd6ba109ce74 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 edad9953aef28c360dfd43a36a9a427ec82c6eb5..818b6b670001a2f07625c9f7c64948c8041f5384 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 00ea67a0e6851d510509e7255fb36c65f595a92c..4034e87147b331243d686812f05739c50bea79bb 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 14f87e2830c88de5666d5d88581a02ec036f1369..674b74d87fffba46ba0de488031147248923bb47 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 af1e469f7e878649a0fc1fa77aae5b487cf6d0a3..90974e07a4773c79f01c0864856d2a7e57eb64c0 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 8b1081964465ae0490a699f76895ab0931fe2279..d45347b6eff93ef0c9c0f75eaad5f3b2e077b328 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 d0725f320d379040041b7242fcb6257182b8f407..cb14830a374507421b0a92873b6c96724e5fabb4 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 c41073cb15a0c44e2425474b636cc635db7e34f6..9863f0705df0b17c15e4b32b252800117be6f14b 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 9e16e42522ed26b59dcb350267b222ff2a965a6a..dfd8eb9fbe53137e6cf1fd313d8f6f630cb916c6 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 763303b1377a71bb8155ef32919061fea9ecba0b..de6787f50431e4ec2164484932abb2fea0f1ff85 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 ac05a3d33c47d42bd7ebbff8fc224c1f115d492d..87fcd271891edd2ebac0e474815f86759dcf2d12 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 050de74cc263c1af511e167ffa1cbf6c8075484e..999c9681875d797622d891245f853036c7db96ae 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 9b2705bab04df9f5ccfc23cb0717d91895944d68..5c11ece8cbe6cc9f2ff91790680ad8fc03ea9a5f 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 f1f15378435589eb160aa8b784239420321ab834..4fb1b9c591ec20ae3e8ee0b1c59b28d213d869a7 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 |