diff --git a/src/graph/context/ast/CypherAstContext.h b/src/graph/context/ast/CypherAstContext.h index eeb0857d5b0..8e5c85473f1 100644 --- a/src/graph/context/ast/CypherAstContext.h +++ b/src/graph/context/ast/CypherAstContext.h @@ -83,6 +83,10 @@ struct Path final { // "(v)-[:like]->()" in (v)-[:like]->() std::string collectVariable; + // Flag for pattern predicate + bool isPred{false}; + bool isAntiPred{false}; + enum PathType : int8_t { kDefault, kAllShortest, kSingleShortest }; PathType pathType{PathType::kDefault}; }; diff --git a/src/graph/executor/CMakeLists.txt b/src/graph/executor/CMakeLists.txt index d33f3b6afa1..0984a387d30 100644 --- a/src/graph/executor/CMakeLists.txt +++ b/src/graph/executor/CMakeLists.txt @@ -40,6 +40,7 @@ nebula_add_library( query/TraverseExecutor.cpp query/AppendVerticesExecutor.cpp query/RollUpApplyExecutor.cpp + query/PatternApplyExecutor.cpp query/GetDstBySrcExecutor.cpp algo/BFSShortestPathExecutor.cpp algo/MultiShortestPathExecutor.cpp diff --git a/src/graph/executor/Executor.cpp b/src/graph/executor/Executor.cpp index 8c0afcb6861..06588f2d1b4 100644 --- a/src/graph/executor/Executor.cpp +++ b/src/graph/executor/Executor.cpp @@ -80,6 +80,7 @@ #include "graph/executor/query/LeftJoinExecutor.h" #include "graph/executor/query/LimitExecutor.h" #include "graph/executor/query/MinusExecutor.h" +#include "graph/executor/query/PatternApplyExecutor.h" #include "graph/executor/query/ProjectExecutor.h" #include "graph/executor/query/RollUpApplyExecutor.h" #include "graph/executor/query/SampleExecutor.h" @@ -542,6 +543,9 @@ Executor *Executor::makeExecutor(QueryContext *qctx, const PlanNode *node) { case PlanNode::Kind::kRollUpApply: { return pool->makeAndAdd(node, qctx); } + case PlanNode::Kind::kPatternApply: { + return pool->makeAndAdd(node, qctx); + } case PlanNode::Kind::kArgument: { return pool->makeAndAdd(node, qctx); } diff --git a/src/graph/executor/query/PatternApplyExecutor.cpp b/src/graph/executor/query/PatternApplyExecutor.cpp new file mode 100644 index 00000000000..826e1c0c86b --- /dev/null +++ b/src/graph/executor/query/PatternApplyExecutor.cpp @@ -0,0 +1,154 @@ +/* Copyright (c) 2022 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#include "graph/executor/query/PatternApplyExecutor.h" + +#include "graph/context/Iterator.h" +#include "graph/context/QueryExpressionContext.h" +#include "graph/planner/plan/Query.h" + +namespace nebula { +namespace graph { + +folly::Future PatternApplyExecutor::execute() { + SCOPED_TIMER(&execTime_); + return patternApply(); +} + +Status PatternApplyExecutor::checkBiInputDataSets() { + auto* patternApply = asNode(node()); + lhsIter_ = ectx_->getResult(patternApply->leftInputVar()).iter(); + DCHECK(!!lhsIter_); + if (lhsIter_->isGetNeighborsIter() || lhsIter_->isDefaultIter()) { + std::stringstream ss; + ss << "PatternApply executor does not support " << lhsIter_->kind(); + return Status::Error(ss.str()); + } + rhsIter_ = ectx_->getResult(patternApply->rightInputVar()).iter(); + DCHECK(!!rhsIter_); + if (rhsIter_->isGetNeighborsIter() || rhsIter_->isDefaultIter()) { + std::stringstream ss; + ss << "PatternApply executor does not support " << rhsIter_->kind(); + return Status::Error(ss.str()); + } + isAntiPred_ = patternApply->isAntiPredicate(); + + return Status::OK(); +} + +void PatternApplyExecutor::collectValidKeys(const std::vector& keyCols, + Iterator* iter, + std::unordered_set& validKeys) const { + QueryExpressionContext ctx(ectx_); + for (; iter->valid(); iter->next()) { + List list; + list.values.reserve(keyCols.size()); + for (auto& col : keyCols) { + Value val = col->eval(ctx(iter)); + list.values.emplace_back(std::move(val)); + } + validKeys.emplace(std::move(list)); + } +} + +void PatternApplyExecutor::collectValidKey(Expression* keyCol, + Iterator* iter, + std::unordered_set& validKey) const { + QueryExpressionContext ctx(ectx_); + for (; iter->valid(); iter->next()) { + auto& val = keyCol->eval(ctx(iter)); + validKey.emplace(val); + } +} + +DataSet PatternApplyExecutor::applyZeroKey(Iterator* appliedIter, const bool allValid) { + DataSet ds; + ds.rows.reserve(appliedIter->size()); + QueryExpressionContext ctx(ectx_); + for (; appliedIter->valid(); appliedIter->next()) { + Row row = mv_ ? appliedIter->moveRow() : *appliedIter->row(); + if (allValid) { + ds.rows.emplace_back(std::move(row)); + } + } + return ds; +} + +DataSet PatternApplyExecutor::applySingleKey(Expression* appliedKey, + Iterator* appliedIter, + const std::unordered_set& validKey) { + DataSet ds; + ds.rows.reserve(appliedIter->size()); + QueryExpressionContext ctx(ectx_); + for (; appliedIter->valid(); appliedIter->next()) { + auto& val = appliedKey->eval(ctx(appliedIter)); + bool applyFlag = (validKey.find(val) != validKey.end()) ^ isAntiPred_; + if (applyFlag) { + Row row = mv_ ? appliedIter->moveRow() : *appliedIter->row(); + ds.rows.emplace_back(std::move(row)); + } + } + return ds; +} + +DataSet PatternApplyExecutor::applyMultiKey(std::vector appliedKeys, + Iterator* appliedIter, + const std::unordered_set& validKeys) { + DataSet ds; + ds.rows.reserve(appliedIter->size()); + QueryExpressionContext ctx(ectx_); + for (; appliedIter->valid(); appliedIter->next()) { + List list; + list.values.reserve(appliedKeys.size()); + for (auto& col : appliedKeys) { + Value val = col->eval(ctx(appliedIter)); + list.values.emplace_back(std::move(val)); + } + + bool applyFlag = (validKeys.find(list) != validKeys.end()) ^ isAntiPred_; + if (applyFlag) { + Row row = mv_ ? appliedIter->moveRow() : *appliedIter->row(); + ds.rows.emplace_back(std::move(row)); + } + } + return ds; +} + +folly::Future PatternApplyExecutor::patternApply() { + auto* patternApplyNode = asNode(node()); + NG_RETURN_IF_ERROR(checkBiInputDataSets()); + + DataSet result; + mv_ = movable(node()->inputVars()[0]); + auto keyCols = patternApplyNode->keyCols(); + if (keyCols.size() == 0) { + // Reverse the valid flag if the pattern predicate is an anti-predicate + applyZeroKey(lhsIter_.get(), (rhsIter_->size() > 0) ^ isAntiPred_); + } else if (keyCols.size() == 1) { + std::unordered_set validKey; + collectValidKey(keyCols[0]->clone(), rhsIter_.get(), validKey); + result = applySingleKey(keyCols[0]->clone(), lhsIter_.get(), validKey); + } else { + // Copy the keyCols to refresh the inside propIndex_ cache + auto cloneExpr = [](std::vector exprs) { + std::vector applyColsCopy; + applyColsCopy.reserve(exprs.size()); + for (auto& expr : exprs) { + applyColsCopy.emplace_back(expr->clone()); + } + return applyColsCopy; + }; + + std::unordered_set validKeys; + collectValidKeys(cloneExpr(keyCols), rhsIter_.get(), validKeys); + result = applyMultiKey(cloneExpr(keyCols), lhsIter_.get(), validKeys); + } + + result.colNames = patternApplyNode->colNames(); + return finish(ResultBuilder().value(Value(std::move(result))).build()); +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/query/PatternApplyExecutor.h b/src/graph/executor/query/PatternApplyExecutor.h new file mode 100644 index 00000000000..0adbcf4cc10 --- /dev/null +++ b/src/graph/executor/query/PatternApplyExecutor.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2022 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#pragma once + +#include "graph/executor/Executor.h" + +namespace nebula { +namespace graph { + +class PatternApplyExecutor : public Executor { + public: + PatternApplyExecutor(const PlanNode* node, QueryContext* qctx) + : Executor("PatternApplyExecutor", node, qctx) {} + + folly::Future execute() override; + + protected: + Status checkBiInputDataSets(); + + void collectValidKeys(const std::vector& keyCols, + Iterator* iter, + std::unordered_set& validKeys) const; + + void collectValidKey(Expression* keyCol, + Iterator* iter, + std::unordered_set& validKey) const; + + DataSet applyZeroKey(Iterator* appliedIter, const bool allValid); + + DataSet applySingleKey(Expression* appliedCol, + Iterator* appliedIter, + const std::unordered_set& validKey); + + DataSet applyMultiKey(std::vector appliedKeys, + Iterator* appliedIter, + const std::unordered_set& validKeys); + + folly::Future patternApply(); + std::unique_ptr lhsIter_; + std::unique_ptr rhsIter_; + + // Should apply the reverse when the pattern is an anti-predicate + bool isAntiPred_{false}; + // Check if the apply side dataset movable + bool mv_{false}; +}; + +} // namespace graph +} // namespace nebula diff --git a/src/graph/optimizer/rule/RemoveNoopProjectRule.cpp b/src/graph/optimizer/rule/RemoveNoopProjectRule.cpp index 19636b6ad27..3b8860d7d2e 100644 --- a/src/graph/optimizer/rule/RemoveNoopProjectRule.cpp +++ b/src/graph/optimizer/rule/RemoveNoopProjectRule.cpp @@ -62,6 +62,7 @@ const std::unordered_set RemoveNoopProjectRule::kQueries{ PlanNode::Kind::kHashInnerJoin, PlanNode::Kind::kCrossJoin, PlanNode::Kind::kRollUpApply, + PlanNode::Kind::kPatternApply, PlanNode::Kind::kArgument, }; diff --git a/src/graph/planner/match/MatchPathPlanner.cpp b/src/graph/planner/match/MatchPathPlanner.cpp index 088d39d1d50..44fb4ecbc11 100644 --- a/src/graph/planner/match/MatchPathPlanner.cpp +++ b/src/graph/planner/match/MatchPathPlanner.cpp @@ -77,7 +77,11 @@ StatusOr MatchPathPlanner::transform(WhereClauseContext* bindWhere, NG_RETURN_IF_ERROR(findStarts(bindWhere, nodeAliasesSeen, startFromEdge, startIndex, subplan)); NG_RETURN_IF_ERROR(expand(startFromEdge, startIndex, subplan)); - MatchSolver::buildProjectColumns(ctx_->qctx, path_, subplan); + // No need to actually build path if the path is just a predicate + if (!path_.isPred) { + MatchSolver::buildProjectColumns(ctx_->qctx, path_, subplan); + } + return subplan; } diff --git a/src/graph/planner/match/SegmentsConnector.cpp b/src/graph/planner/match/SegmentsConnector.cpp index c62c26d5e7a..63dcbcebe8b 100644 --- a/src/graph/planner/match/SegmentsConnector.cpp +++ b/src/graph/planner/match/SegmentsConnector.cpp @@ -93,12 +93,34 @@ SubPlan SegmentsConnector::rollUpApply(CypherClauseContextBase* ctx, return newPlan; } +/*static*/ SubPlan SegmentsConnector::patternApply(CypherClauseContextBase* ctx, + const SubPlan& left, + const SubPlan& right, + const graph::Path& path) { + SubPlan newPlan = left; + auto qctx = ctx->qctx; + std::vector keyProps; + for (const auto& col : path.compareVariables) { + keyProps.emplace_back(FunctionCallExpression::make( + qctx->objPool(), "id", {InputPropertyExpression::make(qctx->objPool(), col)})); + } + auto* patternApply = PatternApply::make( + qctx, left.root, DCHECK_NOTNULL(right.root), std::move(keyProps), path.isAntiPred); + // Left side input may be nullptr, which will be filled later + std::vector colNames = + left.root != nullptr ? left.root->colNames() : ctx->inputColNames; + patternApply->setColNames(std::move(colNames)); + newPlan.root = patternApply; + newPlan.tail = (newPlan.tail == nullptr ? patternApply : newPlan.tail); + return newPlan; +} + SubPlan SegmentsConnector::addInput(const SubPlan& left, const SubPlan& right, bool copyColNames) { if (left.root == nullptr) { return right; } SubPlan newPlan = left; - DCHECK(left.root->isSingleInput()); + if (left.tail->isSingleInput()) { auto* mutableLeft = const_cast(left.tail); auto* siLeft = static_cast(mutableLeft); diff --git a/src/graph/planner/match/SegmentsConnector.h b/src/graph/planner/match/SegmentsConnector.h index 9a201f59820..102d7d7a713 100644 --- a/src/graph/planner/match/SegmentsConnector.h +++ b/src/graph/planner/match/SegmentsConnector.h @@ -44,6 +44,11 @@ class SegmentsConnector final { const SubPlan& right, const graph::Path& path); + static SubPlan patternApply(CypherClauseContextBase* ctx, + const SubPlan& left, + const SubPlan& right, + const graph::Path& path); + /* * left->right */ diff --git a/src/graph/planner/match/WhereClausePlanner.cpp b/src/graph/planner/match/WhereClausePlanner.cpp index a6bf3608da0..0da331924e0 100644 --- a/src/graph/planner/match/WhereClausePlanner.cpp +++ b/src/graph/planner/match/WhereClausePlanner.cpp @@ -19,27 +19,37 @@ StatusOr WhereClausePlanner::transform(CypherClauseContextBase* ctx) { } auto* wctx = static_cast(ctx); - SubPlan wherePlan; - if (wctx->filter) { - auto* newFilter = MatchSolver::doRewrite(wctx->qctx, wctx->aliasesAvailable, wctx->filter); - wherePlan.root = Filter::make(wctx->qctx, nullptr, newFilter, needStableFilter_); - wherePlan.tail = wherePlan.root; - - SubPlan subPlan; - // Build plan for pattern from expression + SubPlan plan; + if (!wctx->paths.empty()) { + SubPlan pathsPlan; + // Build plan for pattern expression for (auto& path : wctx->paths) { auto status = MatchPathPlanner(wctx, path).transform(nullptr, {}); NG_RETURN_IF_ERROR(status); - subPlan = SegmentsConnector::rollUpApply(wctx, subPlan, std::move(status).value(), path); - } - if (subPlan.root != nullptr) { - wherePlan = SegmentsConnector::addInput(wherePlan, subPlan, true); + auto pathPlan = std::move(status).value(); + + if (path.isPred) { + // Build plan for pattern predicates + pathsPlan = SegmentsConnector::patternApply(wctx, pathsPlan, pathPlan, path); + } else { + pathsPlan = SegmentsConnector::rollUpApply(wctx, pathsPlan, pathPlan, path); + } } + plan = pathsPlan; + } - return wherePlan; + if (wctx->filter) { + SubPlan wherePlan; + auto* newFilter = MatchSolver::doRewrite(wctx->qctx, wctx->aliasesAvailable, wctx->filter); + wherePlan.root = Filter::make(wctx->qctx, nullptr, newFilter, needStableFilter_); + wherePlan.tail = wherePlan.root; + if (plan.root == nullptr) { + return wherePlan; + } + plan = SegmentsConnector::addInput(wherePlan, plan, true); } - return wherePlan; + return plan; } } // namespace graph } // namespace nebula diff --git a/src/graph/planner/plan/PlanNode.cpp b/src/graph/planner/plan/PlanNode.cpp index 7221f84eef9..72931b7f24c 100644 --- a/src/graph/planner/plan/PlanNode.cpp +++ b/src/graph/planner/plan/PlanNode.cpp @@ -299,6 +299,8 @@ const char* PlanNode::toString(PlanNode::Kind kind) { return "Argument"; case Kind::kRollUpApply: return "RollUpApply"; + case Kind::kPatternApply: + return "PatternApply"; case Kind::kGetDstBySrc: return "GetDstBySrc"; // no default so the compiler will warning when lack diff --git a/src/graph/planner/plan/PlanNode.h b/src/graph/planner/plan/PlanNode.h index 678acc5b271..0b47e93d91e 100644 --- a/src/graph/planner/plan/PlanNode.h +++ b/src/graph/planner/plan/PlanNode.h @@ -69,6 +69,7 @@ class PlanNode { kHashInnerJoin, kCrossJoin, kRollUpApply, + kPatternApply, kArgument, // Logic diff --git a/src/graph/planner/plan/Query.cpp b/src/graph/planner/plan/Query.cpp index 2383987b78a..a2e5fd952fe 100644 --- a/src/graph/planner/plan/Query.cpp +++ b/src/graph/planner/plan/Query.cpp @@ -944,5 +944,41 @@ PlanNode* RollUpApply::clone() const { return newRollUpApply; } +std::unique_ptr PatternApply::explain() const { + auto desc = BinaryInputNode::explain(); + addDescription("keyCols", folly::toJson(util::toJson(keyCols_)), desc.get()); + return desc; +} + +void PatternApply::accept(PlanNodeVisitor* visitor) { + visitor->visit(this); +} + +PatternApply::PatternApply(QueryContext* qctx, + Kind kind, + PlanNode* left, + PlanNode* right, + std::vector keyCols, + bool isAntiPred) + : BinaryInputNode(qctx, kind, left, right), + keyCols_(std::move(keyCols)), + isAntiPred_(isAntiPred) {} + +void PatternApply::cloneMembers(const PatternApply& r) { + BinaryInputNode::cloneMembers(r); + for (const auto* col : r.keyCols_) { + keyCols_.emplace_back(col->clone()); + } + isAntiPred_ = r.isAntiPred_; +} + +PlanNode* PatternApply::clone() const { + auto* lnode = left() ? left()->clone() : nullptr; + auto* rnode = right() ? right()->clone() : nullptr; + auto* newPatternApply = PatternApply::make(qctx_, lnode, rnode, {}); + newPatternApply->cloneMembers(*this); + return newPatternApply; +} + } // namespace graph } // namespace nebula diff --git a/src/graph/planner/plan/Query.h b/src/graph/planner/plan/Query.h index 24b3c8d03d5..2e188c26d6f 100644 --- a/src/graph/planner/plan/Query.h +++ b/src/graph/planner/plan/Query.h @@ -1809,6 +1809,48 @@ class RollUpApply : public BinaryInputNode { InputPropertyExpression* collectCol_; }; +// PatternApply only used by pattern predicate for now +class PatternApply : public BinaryInputNode { + public: + static PatternApply* make(QueryContext* qctx, + PlanNode* left, + PlanNode* right, + std::vector keyCols, + bool isAntiPred = false) { + return qctx->objPool()->makeAndAdd( + qctx, Kind::kPatternApply, left, right, std::move(keyCols), isAntiPred); + } + + const std::vector& keyCols() const { + return keyCols_; + } + + bool isAntiPredicate() const { + return isAntiPred_; + } + + PlanNode* clone() const override; + std::unique_ptr explain() const override; + + void accept(PlanNodeVisitor* visitor) override; + + protected: + friend ObjectPool; + PatternApply(QueryContext* qctx, + Kind kind, + PlanNode* left, + PlanNode* right, + std::vector keyCols, + bool isAntiPred); + + void cloneMembers(const PatternApply&); + + protected: + // Common columns of subplans on both sides + std::vector keyCols_; + bool isAntiPred_{false}; +}; + } // namespace graph } // namespace nebula #endif // GRAPH_PLANNER_PLAN_QUERY_H_ diff --git a/src/graph/util/ExpressionUtils.cpp b/src/graph/util/ExpressionUtils.cpp index 2975681b372..423a8d39e5b 100644 --- a/src/graph/util/ExpressionUtils.cpp +++ b/src/graph/util/ExpressionUtils.cpp @@ -391,6 +391,32 @@ Expression *ExpressionUtils::rewriteStartsWithExpr(const Expression *expr) { return LogicalExpression::makeAnd(pool, resultLeft, resultRight); } +Expression *ExpressionUtils::foldInnerLogicalExpr(const Expression *originExpr) { + auto matcher = [](const Expression *e) -> bool { + return e->kind() == Expression::Kind::kLogicalAnd || e->kind() == Expression::Kind::kLogicalOr; + }; + auto rewriter = [](const Expression *e) -> Expression * { + auto expr = e->clone(); + auto &operands = static_cast(expr)->operands(); + for (auto iter = operands.begin(); iter != operands.end();) { + if (*iter == nullptr) { + operands.erase(iter); + } else { + iter++; + } + } + auto n = operands.size(); + if (n == 0) { + return nullptr; + } else if (n == 1) { + return operands[0]; + } + return expr; + }; + + return RewriteVisitor::transform(originExpr, std::move(matcher), std::move(rewriter)); +} + Expression *ExpressionUtils::rewriteLogicalAndToLogicalOr(const Expression *expr) { DCHECK(expr->kind() == Expression::Kind::kLogicalAnd); diff --git a/src/graph/util/ExpressionUtils.h b/src/graph/util/ExpressionUtils.h index 5a69cef1998..6df24c2f823 100644 --- a/src/graph/util/ExpressionUtils.h +++ b/src/graph/util/ExpressionUtils.h @@ -104,6 +104,8 @@ class ExpressionUtils { // (A or B) and (C or D) => (A and C) or (A and D) or (B and C) or (B or D) static Expression* rewriteLogicalAndToLogicalOr(const Expression* expr); + static Expression* foldInnerLogicalExpr(const Expression* expr); + // Returns the operands of container expressions // For list and set, return the operands // For map, return the keys diff --git a/src/graph/validator/MatchValidator.cpp b/src/graph/validator/MatchValidator.cpp index aca9729dd8b..318f501dfe0 100644 --- a/src/graph/validator/MatchValidator.cpp +++ b/src/graph/validator/MatchValidator.cpp @@ -5,6 +5,10 @@ #include "graph/validator/MatchValidator.h" +#include "common/expression/FunctionCallExpression.h" +#include "common/expression/LogicalExpression.h" +#include "common/expression/MatchPathPatternExpression.h" +#include "common/expression/UnaryExpression.h" #include "graph/planner/match/MatchSolver.h" #include "graph/util/ExpressionUtils.h" #include "graph/visitor/ExtractGroupSuiteVisitor.h" @@ -133,6 +137,9 @@ Status MatchValidator::validatePath(const MatchPath *path, Path &pathInfo) { NG_RETURN_IF_ERROR(buildNodeInfo(path, pathInfo.nodeInfos, dummy)); NG_RETURN_IF_ERROR(buildEdgeInfo(path, pathInfo.edgeInfos, dummy)); NG_RETURN_IF_ERROR(buildPathExpr(path, pathInfo, dummy)); + pathInfo.isPred = path->isPredicate(); + pathInfo.isAntiPred = path->isAntiPredicate(); + return Status::OK(); } @@ -342,8 +349,8 @@ Status MatchValidator::validateFilter(const Expression *filter, } NG_RETURN_IF_ERROR(validateAliases({whereClauseCtx.filter}, whereClauseCtx.aliasesAvailable)); - NG_RETURN_IF_ERROR(validateMatchPathExpr( - whereClauseCtx.filter, whereClauseCtx.aliasesAvailable, whereClauseCtx.paths)); + NG_RETURN_IF_ERROR( + validatePathInWhere(whereClauseCtx, whereClauseCtx.aliasesAvailable, whereClauseCtx.paths)); return Status::OK(); } @@ -1064,7 +1071,7 @@ Status MatchValidator::validateMatchPathExpr( auto *matchPathExprImpl = const_cast( static_cast(matchPathExpr)); // Check variables - NG_RETURN_IF_ERROR(checkMatchPathExpr(matchPathExprImpl, availableAliases)); + NG_RETURN_IF_ERROR(checkMatchPathExpr(matchPathExprImpl->matchPath(), availableAliases)); // Build path alias auto &matchPath = matchPathExprImpl->matchPath(); auto pathAlias = matchPath.toString(); @@ -1081,10 +1088,121 @@ Status MatchValidator::validateMatchPathExpr( return Status::OK(); } +bool extractSinglePathPredicate(Expression *expr, std::vector &pathPreds) { + if (expr->kind() == Expression::Kind::kMatchPathPattern) { + auto pred = static_cast(expr)->matchPath().clone(); + pred.setPredicate(); + pathPreds.emplace_back(std::move(pred)); + // Absorb expression into path predicate + return true; + } else if (expr->kind() == Expression::Kind::kUnaryNot) { + auto *operand = static_cast(expr)->operand(); + if (operand->kind() == Expression::Kind::kMatchPathPattern) { + auto pred = static_cast(operand)->matchPath().clone(); + pred.setAntiPredicate(); + pathPreds.emplace_back(std::move(pred)); + // Absorb expression into path predicate + return true; + } else if (operand->kind() == Expression::Kind::kFunctionCall) { + auto funcExpr = static_cast(operand); + if (funcExpr->isFunc("exists")) { + auto args = funcExpr->args()->args(); + DCHECK_EQ(args.size(), 1); + if (args[0]->kind() == Expression::Kind::kMatchPathPattern) { + auto pred = static_cast(args[0])->matchPath().clone(); + pred.setAntiPredicate(); + pathPreds.emplace_back(std::move(pred)); + // Absorb expression into path predicate + return true; + } + } + } + } + // Take no effects + return false; +} + +bool extractMultiPathPredicate(Expression *expr, std::vector &pathPreds) { + if (expr->kind() == Expression::Kind::kLogicalAnd) { + auto &operands = static_cast(expr)->operands(); + for (auto iter = operands.begin(); iter != operands.end();) { + if (extractSinglePathPredicate(*iter, pathPreds)) { + // Should remove this operand bcz it was already absorbed into pathPreds + operands.erase(iter); + } else { + iter++; + } + } + // Alread remove inner predicate operands + return false; + } else { + return extractSinglePathPredicate(expr, pathPreds); + } +} + +Status MatchValidator::validatePathInWhere( + WhereClauseContext &wctx, + const std::unordered_map &availableAliases, + std::vector &paths) { + auto expr = ExpressionUtils::flattenInnerLogicalExpr(wctx.filter); + auto *pool = qctx_->objPool(); + ValidatePatternExpressionVisitor visitor(pool, vctx_); + expr->accept(&visitor); + std::vector pathPreds; + // FIXME(czp): Delete this function and add new expression visitor to cover all general cases + if (extractMultiPathPredicate(expr, pathPreds)) { + wctx.filter = nullptr; + } else { + // Flatten and fold the inner logical expressions that already have operands that can be + // compacted + wctx.filter = + ExpressionUtils::foldInnerLogicalExpr(ExpressionUtils::flattenInnerLogicalExpr(expr)); + } + for (auto &pred : pathPreds) { + NG_RETURN_IF_ERROR(checkMatchPathExpr(pred, availableAliases)); + // Build path alias + auto pathAlias = pred.toString(); + pred.setAlias(new std::string(pathAlias)); + paths.emplace_back(); + NG_RETURN_IF_ERROR(validatePath(&pred, paths.back())); + NG_RETURN_IF_ERROR(buildRollUpPathInfo(&pred, paths.back())); + } + + // All inside pattern expressions are path predicate + if (wctx.filter == nullptr) { + return Status::OK(); + } + + ValidatePatternExpressionVisitor pathExprVisitor(pool, vctx_); + wctx.filter->accept(&pathExprVisitor); + auto matchPathExprs = + ExpressionUtils::collectAll(wctx.filter, {Expression::Kind::kMatchPathPattern}); + for (auto &matchPathExpr : matchPathExprs) { + DCHECK_EQ(matchPathExpr->kind(), Expression::Kind::kMatchPathPattern); + auto *matchPathExprImpl = const_cast( + static_cast(matchPathExpr)); + // Check variables + NG_RETURN_IF_ERROR(checkMatchPathExpr(matchPathExprImpl->matchPath(), availableAliases)); + // Build path alias + auto &matchPath = matchPathExprImpl->matchPath(); + auto pathAlias = matchPath.toString(); + matchPath.setAlias(new std::string(pathAlias)); + if (matchPathExprImpl->genList() == nullptr) { + // Don't done in expression visitor + Expression *genList = InputPropertyExpression::make(pool, pathAlias); + matchPathExprImpl->setGenList(genList); + } + paths.emplace_back(); + NG_RETURN_IF_ERROR(validatePath(&matchPath, paths.back())); + NG_RETURN_IF_ERROR(buildRollUpPathInfo(&matchPath, paths.back())); + } + + return Status::OK(); +} + /*static*/ Status MatchValidator::checkMatchPathExpr( - const MatchPathPatternExpression *expr, + const MatchPath &matchPath, const std::unordered_map &availableAliases) { - const auto &matchPath = expr->matchPath(); if (matchPath.alias() != nullptr) { const auto find = availableAliases.find(*matchPath.alias()); if (find == availableAliases.end()) { diff --git a/src/graph/validator/MatchValidator.h b/src/graph/validator/MatchValidator.h index b33feede7dc..d9ca0cba82e 100644 --- a/src/graph/validator/MatchValidator.h +++ b/src/graph/validator/MatchValidator.h @@ -98,8 +98,13 @@ class MatchValidator final : public Validator { const std::unordered_map &availableAliases, std::vector &paths); + // Check and extract path in where clause + Status validatePathInWhere(WhereClauseContext &wctx, + const std::unordered_map &availableAliases, + std::vector &paths); + static Status checkMatchPathExpr( - const MatchPathPatternExpression *expr, + const MatchPath &matchPath, const std::unordered_map &availableAliases); static Status buildRollUpPathInfo(const MatchPath *path, Path &pathInfo); diff --git a/src/parser/MatchPath.h b/src/parser/MatchPath.h index c30a74df35c..a0154f6f022 100644 --- a/src/parser/MatchPath.h +++ b/src/parser/MatchPath.h @@ -335,6 +335,23 @@ class MatchPath final { pathType_ = type; } + bool isPredicate() const { + return isPred_; + } + + void setPredicate() { + isPred_ = true; + } + + bool isAntiPredicate() const { + return isPred_ && isAntiPred_; + } + + void setAntiPredicate() { + isPred_ = true; + isAntiPred_ = true; + } + std::string toString() const; MatchPath clone() const { @@ -353,6 +370,9 @@ class MatchPath final { std::vector> nodes_; std::vector> edges_; PathType pathType_{PathType::kDefault}; + + bool isPred_{false}; + bool isAntiPred_{false}; }; } // namespace nebula