diff --git a/src/common/expression/PropertyExpression.h b/src/common/expression/PropertyExpression.h index a9baf8d47a7..3b7bd5e26cf 100644 --- a/src/common/expression/PropertyExpression.h +++ b/src/common/expression/PropertyExpression.h @@ -160,10 +160,10 @@ class LabelTagPropertyExpression final : public PropertyExpression { } private: - LabelTagPropertyExpression(ObjectPool* pool, - Expression* label = nullptr, - const std::string& tag = "", - const std::string& prop = "") + explicit LabelTagPropertyExpression(ObjectPool* pool, + Expression* label = nullptr, + const std::string& tag = "", + const std::string& prop = "") : PropertyExpression(pool, Kind::kLabelTagProperty, "", tag, prop), label_(label) {} void writeTo(Encoder& encoder) const override; diff --git a/src/daemons/CMakeLists.txt b/src/daemons/CMakeLists.txt index 2600e7dd3d5..04f6cafd646 100644 --- a/src/daemons/CMakeLists.txt +++ b/src/daemons/CMakeLists.txt @@ -128,6 +128,7 @@ nebula_add_executable( $ $ $ + $ $ $ $ @@ -211,6 +212,7 @@ nebula_add_executable( $ $ $ + $ $ $ $ diff --git a/src/graph/optimizer/Optimizer.cpp b/src/graph/optimizer/Optimizer.cpp index 320a87b3529..de570d6bac6 100644 --- a/src/graph/optimizer/Optimizer.cpp +++ b/src/graph/optimizer/Optimizer.cpp @@ -12,6 +12,7 @@ #include "graph/planner/plan/ExecutionPlan.h" #include "graph/planner/plan/Logic.h" #include "graph/planner/plan/PlanNode.h" +#include "graph/visitor/PrunePropertiesVisitor.h" using nebula::graph::BinaryInputNode; using nebula::graph::Loop; @@ -20,6 +21,8 @@ using nebula::graph::QueryContext; using nebula::graph::Select; using nebula::graph::SingleDependencyNode; +DEFINE_bool(enable_optimizer_property_pruner_rule, true, ""); + namespace nebula { namespace opt { @@ -30,12 +33,32 @@ StatusOr Optimizer::findBestPlan(QueryContext *qctx) { auto optCtx = std::make_unique(qctx); auto root = qctx->plan()->root(); - auto status = prepare(optCtx.get(), root); - NG_RETURN_IF_ERROR(status); - auto rootGroup = std::move(status).value(); + auto spaceID = qctx->rctx()->session()->space().id; + + auto ret = prepare(optCtx.get(), root); + NG_RETURN_IF_ERROR(ret); + auto rootGroup = std::move(ret).value(); NG_RETURN_IF_ERROR(doExploration(optCtx.get(), rootGroup)); - return rootGroup->getPlan(); + auto *newRoot = rootGroup->getPlan(); + + auto status2 = postprocess(const_cast(newRoot), qctx, spaceID); + if (!status2.ok()) { + LOG(ERROR) << "Failed to postprocess plan: " << status2; + } + return newRoot; +} + +Status Optimizer::postprocess(PlanNode *root, graph::QueryContext *qctx, GraphSpaceID spaceID) { + if (FLAGS_enable_optimizer_property_pruner_rule) { + graph::PropertyTracker propsUsed; + graph::PrunePropertiesVisitor visitor(propsUsed, qctx, spaceID); + root->accept(&visitor); + if (!visitor.ok()) { + return visitor.status(); + } + } + return Status::OK(); } StatusOr Optimizer::prepare(OptContext *ctx, PlanNode *root) { diff --git a/src/graph/optimizer/Optimizer.h b/src/graph/optimizer/Optimizer.h index 803a06bd417..b09a48ef4a8 100644 --- a/src/graph/optimizer/Optimizer.h +++ b/src/graph/optimizer/Optimizer.h @@ -8,6 +8,9 @@ #include "common/base/Base.h" #include "common/base/StatusOr.h" +#include "common/thrift/ThriftTypes.h" + +DECLARE_bool(enable_optimizer_property_pruner_rule); namespace nebula { namespace graph { @@ -30,7 +33,10 @@ class Optimizer final { StatusOr findBestPlan(graph::QueryContext *qctx); private: + Status postprocess(graph::PlanNode *root, graph::QueryContext *qctx, GraphSpaceID spaceID); + StatusOr prepare(OptContext *ctx, graph::PlanNode *root); + Status doExploration(OptContext *octx, OptGroup *rootGroup); OptGroup *convertToGroup(OptContext *ctx, diff --git a/src/graph/optimizer/rule/CollapseProjectRule.cpp b/src/graph/optimizer/rule/CollapseProjectRule.cpp index 6a8ae3d552d..209f8eb5195 100644 --- a/src/graph/optimizer/rule/CollapseProjectRule.cpp +++ b/src/graph/optimizer/rule/CollapseProjectRule.cpp @@ -11,6 +11,8 @@ #include "graph/planner/plan/Query.h" #include "graph/util/ExpressionUtils.h" +DEFINE_bool(enable_optimizer_collapse_project_rule, true, ""); + using nebula::graph::PlanNode; using nebula::graph::QueryContext; @@ -110,6 +112,9 @@ StatusOr CollapseProjectRule::transform( } bool CollapseProjectRule::match(OptContext* octx, const MatchedResult& matched) const { + if (!FLAGS_enable_optimizer_collapse_project_rule) { + return false; + } return OptRule::match(octx, matched); } diff --git a/src/graph/optimizer/rule/CollapseProjectRule.h b/src/graph/optimizer/rule/CollapseProjectRule.h index 3b57da7cfa0..50c6bd5e77c 100644 --- a/src/graph/optimizer/rule/CollapseProjectRule.h +++ b/src/graph/optimizer/rule/CollapseProjectRule.h @@ -8,6 +8,8 @@ #include "graph/optimizer/OptRule.h" +DECLARE_bool(enable_optimizer_collapse_project_rule); + namespace nebula { namespace opt { diff --git a/src/graph/optimizer/test/CMakeLists.txt b/src/graph/optimizer/test/CMakeLists.txt index f4fd79ff81f..f53898b4b9f 100644 --- a/src/graph/optimizer/test/CMakeLists.txt +++ b/src/graph/optimizer/test/CMakeLists.txt @@ -43,6 +43,7 @@ set(OPTIMIZER_TEST_LIB $ $ $ + $ $ $ $ diff --git a/src/graph/planner/plan/PlanNode.cpp b/src/graph/planner/plan/PlanNode.cpp index 77ceecacf8d..22fb40e7433 100644 --- a/src/graph/planner/plan/PlanNode.cpp +++ b/src/graph/planner/plan/PlanNode.cpp @@ -13,6 +13,7 @@ #include "common/graph/Response.h" #include "graph/context/QueryContext.h" +#include "graph/planner/plan/PlanNodeVisitor.h" #include "graph/util/ToJson.h" namespace nebula { @@ -360,6 +361,10 @@ std::unique_ptr PlanNode::explain() const { return desc; } +void PlanNode::accept(PlanNodeVisitor* visitor) { + visitor->visit(this); +} + void PlanNode::releaseSymbols() { auto symTbl = qctx_->symTable(); for (auto in : inputVars_) { diff --git a/src/graph/planner/plan/PlanNode.h b/src/graph/planner/plan/PlanNode.h index 5d50b3ac4e4..35e19b70b87 100644 --- a/src/graph/planner/plan/PlanNode.h +++ b/src/graph/planner/plan/PlanNode.h @@ -14,6 +14,8 @@ namespace nebula { namespace graph { +class PlanNodeVisitor; + /** * PlanNode is an abstraction of nodes in an execution plan which * is a kind of directed cyclic graph. @@ -189,6 +191,12 @@ class PlanNode { // Describe plan node virtual std::unique_ptr explain() const; + virtual void accept(PlanNodeVisitor* visitor); + + void markDeleted() { + deleted_ = true; + } + virtual PlanNode* clone() const = 0; virtual void calcCost(); @@ -311,6 +319,7 @@ class PlanNode { std::vector dependencies_; std::vector inputVars_; std::vector outputVars_; + bool deleted_{false}; }; std::ostream& operator<<(std::ostream& os, PlanNode::Kind kind); diff --git a/src/graph/planner/plan/PlanNodeVisitor.h b/src/graph/planner/plan/PlanNodeVisitor.h new file mode 100644 index 00000000000..eb8b6d8d0da --- /dev/null +++ b/src/graph/planner/plan/PlanNodeVisitor.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#ifndef PLAN_PLANNODEVISITOR_H_ +#define PLAN_PLANNODEVISITOR_H_ + +#include "graph/planner/plan/PlanNode.h" +#include "graph/planner/plan/Query.h" + +namespace nebula { +namespace graph { + +class PlanNodeVisitor { + public: + virtual ~PlanNodeVisitor() = default; + + virtual void visit(PlanNode *node) = 0; + virtual void visit(Filter *node) = 0; + virtual void visit(Project *node) = 0; + virtual void visit(Aggregate *node) = 0; + virtual void visit(Traverse *node) = 0; + virtual void visit(AppendVertices *node) = 0; + virtual void visit(BiJoin *node) = 0; +}; + +} // namespace graph +} // namespace nebula + +#endif // PLAN_PLANNODEVISITOR_H_ diff --git a/src/graph/planner/plan/Query.cpp b/src/graph/planner/plan/Query.cpp index 5e133e82aac..0c1177047f2 100644 --- a/src/graph/planner/plan/Query.cpp +++ b/src/graph/planner/plan/Query.cpp @@ -10,6 +10,7 @@ #include #include +#include "graph/planner/plan/PlanNodeVisitor.h" #include "graph/util/ExpressionUtils.h" #include "graph/util/ToJson.h" @@ -269,6 +270,10 @@ std::unique_ptr Filter::explain() const { return desc; } +void Filter::accept(PlanNodeVisitor* visitor) { + visitor->visit(this); +} + PlanNode* Filter::clone() const { auto* newFilter = Filter::make(qctx_, nullptr); newFilter->cloneMembers(*this); @@ -336,6 +341,10 @@ std::unique_ptr Project::explain() const { return desc; } +void Project::accept(PlanNodeVisitor* visitor) { + visitor->visit(this); +} + PlanNode* Project::clone() const { auto* newProj = Project::make(qctx_, nullptr); newProj->cloneMembers(*this); @@ -490,6 +499,10 @@ std::unique_ptr Aggregate::explain() const { return desc; } +void Aggregate::accept(PlanNodeVisitor* visitor) { + visitor->visit(this); +} + PlanNode* Aggregate::clone() const { auto* newAggregate = Aggregate::make(qctx_, nullptr); newAggregate->cloneMembers(*this); @@ -734,6 +747,10 @@ std::unique_ptr Traverse::explain() const { return desc; } +void Traverse::accept(PlanNodeVisitor* visitor) { + visitor->visit(this); +} + AppendVertices* AppendVertices::clone() const { auto newAV = AppendVertices::make(qctx_, nullptr, space_); newAV->cloneMembers(*this); @@ -760,6 +777,10 @@ std::unique_ptr AppendVertices::explain() const { return desc; } +void AppendVertices::accept(PlanNodeVisitor* visitor) { + visitor->visit(this); +} + std::unique_ptr BiJoin::explain() const { auto desc = BinaryInputNode::explain(); addDescription("hashKeys", folly::toJson(util::toJson(hashKeys_)), desc.get()); @@ -767,6 +788,10 @@ std::unique_ptr BiJoin::explain() const { return desc; } +void BiJoin::accept(PlanNodeVisitor* visitor) { + visitor->visit(this); +} + void BiJoin::cloneMembers(const BiJoin& j) { BinaryInputNode::cloneMembers(j); diff --git a/src/graph/planner/plan/Query.h b/src/graph/planner/plan/Query.h index 48cdce7f335..a9e8a99d145 100644 --- a/src/graph/planner/plan/Query.h +++ b/src/graph/planner/plan/Query.h @@ -730,6 +730,8 @@ class Filter final : public SingleInputNode { PlanNode* clone() const override; std::unique_ptr explain() const override; + void accept(PlanNodeVisitor* visitor) override; + private: Filter(QueryContext* qctx, PlanNode* input, Expression* condition, bool needStableFilter); void cloneMembers(const Filter&); @@ -826,6 +828,8 @@ class Project final : public SingleInputNode { PlanNode* clone() const override; std::unique_ptr explain() const override; + void accept(PlanNodeVisitor* visitor) override; + private: Project(QueryContext* qctx, PlanNode* input, YieldColumns* cols); @@ -1114,6 +1118,8 @@ class Aggregate final : public SingleInputNode { PlanNode* clone() const override; std::unique_ptr explain() const override; + void accept(PlanNodeVisitor* visitor) override; + private: Aggregate(QueryContext* qctx, PlanNode* input, @@ -1469,6 +1475,8 @@ class Traverse final : public GetNeighbors { std::unique_ptr explain() const override; + void accept(PlanNodeVisitor* visitor) override; + Traverse* clone() const override; MatchStepRange* stepRange() const { @@ -1526,6 +1534,8 @@ class AppendVertices final : public GetVertices { std::unique_ptr explain() const override; + void accept(PlanNodeVisitor* visitor) override; + AppendVertices* clone() const override; Expression* vFilter() const { @@ -1585,6 +1595,8 @@ class BiJoin : public BinaryInputNode { std::unique_ptr explain() const override; + void accept(PlanNodeVisitor* visitor) override; + protected: BiJoin(QueryContext* qctx, Kind kind, diff --git a/src/graph/util/ExpressionUtils.cpp b/src/graph/util/ExpressionUtils.cpp index 3926c5411c7..42d57d2d3b2 100644 --- a/src/graph/util/ExpressionUtils.cpp +++ b/src/graph/util/ExpressionUtils.cpp @@ -17,6 +17,7 @@ #include "graph/context/QueryContext.h" #include "graph/context/QueryExpressionContext.h" #include "graph/visitor/FoldConstantExprVisitor.h" +#include "graph/visitor/PropertyTrackerVisitor.h" DEFINE_int32(max_expression_depth, 512, "Max depth of expression tree."); diff --git a/src/graph/visitor/CMakeLists.txt b/src/graph/visitor/CMakeLists.txt index c02f9d91f6a..77c38ebf19a 100644 --- a/src/graph/visitor/CMakeLists.txt +++ b/src/graph/visitor/CMakeLists.txt @@ -6,6 +6,7 @@ nebula_add_library( expr_visitor_obj OBJECT ExprVisitorImpl.cpp DeducePropsVisitor.cpp + PropertyTrackerVisitor.cpp DeduceTypeVisitor.cpp ExtractPropExprVisitor.cpp ExtractFilterExprVisitor.cpp @@ -17,4 +18,9 @@ nebula_add_library( EvaluableExprVisitor.cpp ) +nebula_add_library( + plan_node_visitor_obj OBJECT + PrunePropertiesVisitor.cpp +) + nebula_add_subdirectory(test) diff --git a/src/graph/visitor/PropertyTrackerVisitor.cpp b/src/graph/visitor/PropertyTrackerVisitor.cpp new file mode 100644 index 00000000000..99e62836579 --- /dev/null +++ b/src/graph/visitor/PropertyTrackerVisitor.cpp @@ -0,0 +1,263 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#include "graph/visitor/PropertyTrackerVisitor.h" + +#include +#include + +#include "common/expression/Expression.h" +#include "graph/context/QueryContext.h" + +namespace nebula { +namespace graph { + +Status PropertyTracker::update(const std::string &oldName, const std::string &newName) { + if (oldName == newName) { + return Status::OK(); + } + + auto it1 = vertexPropsMap.find(oldName); + bool hasNodeAlias = it1 != vertexPropsMap.end(); + auto it2 = edgePropsMap.find(oldName); + bool hasEdgeAlias = it2 != edgePropsMap.end(); + if (hasNodeAlias && hasEdgeAlias) { + return Status::Error("Duplicated property name: %s", oldName.c_str()); + } + if (hasNodeAlias) { + vertexPropsMap[newName] = std::move(it1->second); + vertexPropsMap.erase(it1); + } + if (hasEdgeAlias) { + edgePropsMap[newName] = std::move(it2->second); + edgePropsMap.erase(it2); + } + + auto it3 = colsSet.find(oldName); + if (it3 != colsSet.end()) { + colsSet.erase(it3); + colsSet.insert(newName); + } + + return Status::OK(); +} + +bool PropertyTracker::hasAlias(const std::string &name) const { + return vertexPropsMap.find(name) != vertexPropsMap.end() || + edgePropsMap.find(name) != edgePropsMap.end() || colsSet.find(name) != colsSet.end(); +} + +PropertyTrackerVisitor::PropertyTrackerVisitor(const QueryContext *qctx, + GraphSpaceID space, + PropertyTracker &propsUsed, + const std::string &entityAlias) + : qctx_(qctx), space_(space), propsUsed_(propsUsed), entityAlias_(entityAlias) { + DCHECK(qctx != nullptr); +} + +void PropertyTrackerVisitor::visit(TagPropertyExpression *expr) { + auto &tagName = expr->sym(); + auto &propName = expr->prop(); + auto ret = qctx_->schemaMng()->toTagID(space_, tagName); + if (!ret.ok()) { + status_ = std::move(ret).status(); + return; + } + auto tagId = ret.value(); + propsUsed_.vertexPropsMap[entityAlias_][tagId].emplace(propName); +} + +void PropertyTrackerVisitor::visit(EdgePropertyExpression *expr) { + auto &edgeName = expr->sym(); + auto &propName = expr->prop(); + auto ret = qctx_->schemaMng()->toEdgeType(space_, edgeName); + if (!ret.ok()) { + status_ = std::move(ret).status(); + return; + } + auto edgeType = ret.value(); + propsUsed_.edgePropsMap[entityAlias_][edgeType].emplace(propName); +} + +void PropertyTrackerVisitor::visit(LabelTagPropertyExpression *expr) { + auto status = qctx_->schemaMng()->toTagID(space_, expr->sym()); + if (!status.ok()) { + status_ = std::move(status).status(); + return; + } + auto &nodeAlias = static_cast(expr->label())->prop(); + auto &tagName = expr->sym(); + auto &propName = expr->prop(); + auto ret = qctx_->schemaMng()->toTagID(space_, tagName); + if (!ret.ok()) { + status_ = std::move(ret).status(); + return; + } + auto tagId = ret.value(); + propsUsed_.vertexPropsMap[nodeAlias][tagId].emplace(propName); +} + +void PropertyTrackerVisitor::visit(InputPropertyExpression *expr) { + auto &colName = expr->prop(); + propsUsed_.colsSet.emplace(colName); +} + +void PropertyTrackerVisitor::visit(VariablePropertyExpression *expr) { + auto &colName = expr->prop(); + propsUsed_.colsSet.emplace(colName); +} + +void PropertyTrackerVisitor::visit(AttributeExpression *expr) { + auto *lhs = expr->left(); + auto *rhs = expr->right(); + if (rhs->kind() != Expression::Kind::kConstant) { + return; + } + auto *constExpr = static_cast(rhs); + auto &constVal = constExpr->value(); + if (constVal.type() != Value::Type::STRING) { + return; + } + auto &propName = constVal.getStr(); + static const int kUnknownEdgeType = 0; + switch (lhs->kind()) { + case Expression::Kind::kVarProperty: { // $e.name + auto *varPropExpr = static_cast(lhs); + auto &edgeAlias = varPropExpr->prop(); + propsUsed_.edgePropsMap[edgeAlias][kUnknownEdgeType].emplace(propName); + break; + } + case Expression::Kind::kInputProperty: { + auto *inputPropExpr = static_cast(lhs); + auto &edgeAlias = inputPropExpr->prop(); + propsUsed_.edgePropsMap[edgeAlias][kUnknownEdgeType].emplace(propName); + break; + } + case Expression::Kind::kSubscript: { // $-.e[0].name + auto *subscriptExpr = static_cast(lhs); + auto *subLeftExpr = subscriptExpr->left(); + if (subLeftExpr->kind() == Expression::Kind::kInputProperty) { + auto *inputPropExpr = static_cast(subLeftExpr); + auto &edgeAlias = inputPropExpr->prop(); + propsUsed_.edgePropsMap[edgeAlias][kUnknownEdgeType].emplace(propName); + } + } + default: + break; + } +} + +void PropertyTrackerVisitor::visit(FunctionCallExpression *expr) { + static const std::unordered_set kVertexIgnoreFuncs = {"id"}; + static const std::unordered_set kEdgeIgnoreFuncs = { + "src", "dst", "type", "typeid", "rank"}; + + auto funName = expr->name(); + if (kVertexIgnoreFuncs.find(funName) != kVertexIgnoreFuncs.end()) { + DCHECK_EQ(expr->args()->numArgs(), 1); + auto argExpr = expr->args()->args()[0]; + auto nodeAlias = extractColNameFromInputPropOrVarPropExpr(argExpr); + if (!nodeAlias.empty()) { + auto it = propsUsed_.vertexPropsMap.find(nodeAlias); + if (it == propsUsed_.vertexPropsMap.end()) { + propsUsed_.vertexPropsMap[nodeAlias] = {}; + } + } + return; + } else if (kEdgeIgnoreFuncs.find(funName) != kEdgeIgnoreFuncs.end()) { + DCHECK_EQ(expr->args()->numArgs(), 1); + auto argExpr = expr->args()->args()[0]; + auto edgeAlias = extractColNameFromInputPropOrVarPropExpr(argExpr); + if (!edgeAlias.empty()) { + auto it = propsUsed_.edgePropsMap.find(edgeAlias); + if (it == propsUsed_.edgePropsMap.end()) { + propsUsed_.edgePropsMap[edgeAlias] = {}; + } + } + return; + } + + for (auto *arg : expr->args()->args()) { + arg->accept(this); + if (!ok()) { + break; + } + } +} + +void PropertyTrackerVisitor::visit(DestPropertyExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(SourcePropertyExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(EdgeSrcIdExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(EdgeTypeExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(EdgeRankExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(EdgeDstIdExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(UUIDExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(VariableExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(VersionedVariableExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(LabelExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(LabelAttributeExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(ConstantExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(ColumnExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(VertexExpression *expr) { + UNUSED(expr); +} + +void PropertyTrackerVisitor::visit(EdgeExpression *expr) { + UNUSED(expr); +} + +std::string PropertyTrackerVisitor::extractColNameFromInputPropOrVarPropExpr( + const Expression *expr) { + if (expr->kind() == Expression::Kind::kInputProperty) { + auto *inputPropExpr = static_cast(expr); + return inputPropExpr->prop(); + } else if (expr->kind() == Expression::Kind::kVarProperty) { + auto *varPropExpr = static_cast(expr); + return varPropExpr->prop(); + } + return ""; +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/visitor/PropertyTrackerVisitor.h b/src/graph/visitor/PropertyTrackerVisitor.h new file mode 100644 index 00000000000..e8750391656 --- /dev/null +++ b/src/graph/visitor/PropertyTrackerVisitor.h @@ -0,0 +1,86 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#ifndef GRAPH_VISITOR_PROPERTYTRACKERVISITOR_H +#define GRAPH_VISITOR_PROPERTYTRACKERVISITOR_H + +#include "common/base/Status.h" +#include "common/thrift/ThriftTypes.h" +#include "graph/visitor/ExprVisitorImpl.h" + +namespace nebula { + +class Expression; + +namespace graph { + +class QueryContext; + +struct PropertyTracker { + std::unordered_map>> + vertexPropsMap; + std::unordered_map>> + edgePropsMap; + std::unordered_set colsSet; + + Status update(const std::string& oldName, const std::string& newName); + bool hasAlias(const std::string& name) const; +}; + +class PropertyTrackerVisitor : public ExprVisitorImpl { + public: + PropertyTrackerVisitor(const QueryContext* qctx, + GraphSpaceID space, + PropertyTracker& propsUsed, + const std::string& entityAlias); + + bool ok() const override { + return status_.ok(); + } + + const Status& status() const { + return status_; + } + + private: + using ExprVisitorImpl::visit; + void visit(TagPropertyExpression* expr) override; + void visit(EdgePropertyExpression* expr) override; + void visit(LabelTagPropertyExpression* expr) override; + void visit(InputPropertyExpression* expr) override; + void visit(VariablePropertyExpression* expr) override; + void visit(AttributeExpression* expr) override; + void visit(FunctionCallExpression* expr) override; + + void visit(SourcePropertyExpression* expr) override; + void visit(DestPropertyExpression* expr) override; + void visit(EdgeSrcIdExpression* expr) override; + void visit(EdgeTypeExpression* expr) override; + void visit(EdgeRankExpression* expr) override; + void visit(EdgeDstIdExpression* expr) override; + void visit(UUIDExpression* expr) override; + void visit(VariableExpression* expr) override; + void visit(VersionedVariableExpression* expr) override; + void visit(LabelExpression* expr) override; + void visit(LabelAttributeExpression* expr) override; + void visit(ConstantExpression* expr) override; + void visit(VertexExpression* expr) override; + void visit(EdgeExpression* expr) override; + void visit(ColumnExpression* expr) override; + + std::string extractColNameFromInputPropOrVarPropExpr(const Expression* expr); + + const QueryContext* qctx_{nullptr}; + GraphSpaceID space_; + PropertyTracker& propsUsed_; + std::string entityAlias_; + + Status status_; +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_VISITOR_PROPERTYTRACKERVISITOR_H diff --git a/src/graph/visitor/PrunePropertiesVisitor.cpp b/src/graph/visitor/PrunePropertiesVisitor.cpp new file mode 100644 index 00000000000..6207b107c22 --- /dev/null +++ b/src/graph/visitor/PrunePropertiesVisitor.cpp @@ -0,0 +1,288 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#include "graph/visitor/PrunePropertiesVisitor.h" + +namespace nebula { +namespace graph { + +PrunePropertiesVisitor::PrunePropertiesVisitor(PropertyTracker &propsUsed, + graph::QueryContext *qctx, + GraphSpaceID spaceID) + : propsUsed_(propsUsed), qctx_(qctx), spaceID_(spaceID) { + DCHECK(qctx_ != nullptr); +} + +void PrunePropertiesVisitor::visit(PlanNode *node) { + status_ = depsPruneProperties(node->dependencies()); +} + +void PrunePropertiesVisitor::visit(Filter *node) { + if (node->condition() != nullptr) { + status_ = extractPropsFromExpr(node->condition()); + if (!status_.ok()) { + return; + } + } + + status_ = depsPruneProperties(node->dependencies()); +} + +void PrunePropertiesVisitor::visit(Project *node) { + if (node->columns()) { + const auto &columns = node->columns()->columns(); + auto &colNames = node->colNames(); + std::vector aliasExists(colNames.size(), false); + for (size_t i = 0; i < columns.size(); ++i) { + aliasExists[i] = propsUsed_.hasAlias(colNames[i]); + } + for (size_t i = 0; i < columns.size(); ++i) { + auto *col = DCHECK_NOTNULL(columns[i]); + auto *expr = col->expr(); + auto &alias = colNames[i]; + // If the alias exists, try to rename alias + if (aliasExists[i]) { + if (expr->kind() == Expression::Kind::kInputProperty) { + auto *inputPropExpr = static_cast(expr); + auto &newAlias = inputPropExpr->prop(); + status_ = propsUsed_.update(alias, newAlias); + if (!status_.ok()) { + return; + } + } else if (expr->kind() == Expression::Kind::kVarProperty) { + auto *varPropExpr = static_cast(expr); + auto &newAlias = varPropExpr->prop(); + status_ = propsUsed_.update(alias, newAlias); + if (!status_.ok()) { + return; + } + } else { // eg. "PathBuild[$-.x,$-.__VAR_0,$-.y] AS p" + // How to handle this case? + propsUsed_.colsSet.erase(alias); + status_ = extractPropsFromExpr(expr); + if (!status_.ok()) { + return; + } + } + } else { + // Otherwise, extract properties from the column expression + status_ = extractPropsFromExpr(expr); + if (!status_.ok()) { + return; + } + } + } + } + + status_ = depsPruneProperties(node->dependencies()); +} + +void PrunePropertiesVisitor::visit(Aggregate *node) { + for (auto *groupKey : node->groupKeys()) { + status_ = extractPropsFromExpr(groupKey); + if (!status_.ok()) { + return; + } + } + for (auto *groupItem : node->groupItems()) { + status_ = extractPropsFromExpr(groupItem); + if (!status_.ok()) { + return; + } + } + status_ = depsPruneProperties(node->dependencies()); +} + +void PrunePropertiesVisitor::visit(Traverse *node) { + auto &colNames = node->colNames(); + DCHECK_GE(colNames.size(), 2); + auto &nodeAlias = colNames[colNames.size() - 2]; + auto &edgeAlias = colNames.back(); + + if (node->vFilter() != nullptr) { + status_ = extractPropsFromExpr(node->vFilter(), nodeAlias); + if (!status_.ok()) { + return; + } + } + + if (node->eFilter() != nullptr) { + status_ = extractPropsFromExpr(node->eFilter(), edgeAlias); + if (!status_.ok()) { + return; + } + } + + auto *vertexProps = node->vertexProps(); + if (propsUsed_.colsSet.find(nodeAlias) == propsUsed_.colsSet.end() && vertexProps != nullptr) { + auto it2 = propsUsed_.vertexPropsMap.find(nodeAlias); + if (it2 == propsUsed_.vertexPropsMap.end()) { // nodeAlias is not used + node->setVertexProps(nullptr); + } else { + auto prunedVertexProps = std::make_unique>(); + auto &usedVertexProps = it2->second; + if (usedVertexProps.empty()) { + node->setVertexProps(nullptr); + status_ = depsPruneProperties(node->dependencies()); + return; + } + prunedVertexProps->reserve(usedVertexProps.size()); + for (auto &vertexProp : *vertexProps) { + auto tagId = vertexProp.tag_ref().value(); + auto &props = vertexProp.props_ref().value(); + auto it3 = usedVertexProps.find(tagId); + if (it3 != usedVertexProps.end()) { + auto &usedProps = it3->second; + VertexProp newVProp; + newVProp.tag_ref() = tagId; + std::vector newProps; + for (auto &prop : props) { + if (usedProps.find(prop) != usedProps.end()) { + newProps.emplace_back(prop); + } + } + newVProp.props_ref() = std::move(newProps); + prunedVertexProps->emplace_back(std::move(newVProp)); + } + } + node->setVertexProps(std::move(prunedVertexProps)); + } + } + + static const std::unordered_set reservedEdgeProps = { + nebula::kSrc, nebula::kType, nebula::kRank, nebula::kDst}; + auto *edgeProps = node->edgeProps(); + if (propsUsed_.colsSet.find(edgeAlias) == propsUsed_.colsSet.end() && edgeProps != nullptr) { + auto prunedEdgeProps = std::make_unique>(); + prunedEdgeProps->reserve(edgeProps->size()); + auto it2 = propsUsed_.edgePropsMap.find(edgeAlias); + + for (auto &edgeProp : *edgeProps) { + auto edgeType = edgeProp.type_ref().value(); + auto &props = edgeProp.props_ref().value(); + EdgeProp newEProp; + newEProp.type_ref() = edgeType; + std::vector newProps{reservedEdgeProps.begin(), reservedEdgeProps.end()}; + std::unordered_set usedProps; + if (it2 != propsUsed_.edgePropsMap.end()) { + auto &usedEdgeProps = it2->second; + auto it3 = usedEdgeProps.find(std::abs(edgeType)); + if (it3 != usedEdgeProps.end()) { + usedProps = {it3->second.begin(), it3->second.end()}; + } + static const int kUnknownEdgeType = 0; + auto it4 = usedEdgeProps.find(kUnknownEdgeType); + if (it4 != usedEdgeProps.end()) { + usedProps.insert(it4->second.begin(), it4->second.end()); + } + } + for (auto &prop : props) { + if (reservedEdgeProps.find(prop) == reservedEdgeProps.end() && + usedProps.find(prop) != usedProps.end()) { + newProps.emplace_back(prop); + } + } + newEProp.props_ref() = std::move(newProps); + prunedEdgeProps->emplace_back(std::move(newEProp)); + } + node->setEdgeProps(std::move(prunedEdgeProps)); + } + + status_ = depsPruneProperties(node->dependencies()); +} + +// AppendVertices should be deleted when no properties it pulls are used by the parent node. +void PrunePropertiesVisitor::visit(AppendVertices *node) { + auto &colNames = node->colNames(); + DCHECK(!colNames.empty()); + auto &nodeAlias = colNames.back(); + auto it = propsUsed_.colsSet.find(nodeAlias); + if (it != propsUsed_.colsSet.end()) { // All properties are used + // propsUsed_.colsSet.erase(it); + status_ = depsPruneProperties(node->dependencies()); + return; + } + + if (node->vFilter() != nullptr) { + status_ = extractPropsFromExpr(node->vFilter(), nodeAlias); + return; + } + auto *vertexProps = node->props(); + if (vertexProps != nullptr) { + auto prunedVertexProps = std::make_unique>(); + auto it2 = propsUsed_.vertexPropsMap.find(nodeAlias); + if (it2 != propsUsed_.vertexPropsMap.end()) { + auto &usedVertexProps = it2->second; + if (usedVertexProps.empty()) { + node->markDeleted(); + status_ = depsPruneProperties(node->dependencies()); + return; + } + prunedVertexProps->reserve(usedVertexProps.size()); + for (auto &vertexProp : *vertexProps) { + auto tagId = vertexProp.tag_ref().value(); + auto &props = vertexProp.props_ref().value(); + auto it3 = usedVertexProps.find(tagId); + if (it3 != usedVertexProps.end()) { + auto &usedProps = it3->second; + VertexProp newVProp; + newVProp.tag_ref() = tagId; + std::vector newProps; + for (auto &prop : props) { + if (usedProps.find(prop) != usedProps.end()) { + newProps.emplace_back(prop); + } + } + newVProp.props_ref() = std::move(newProps); + prunedVertexProps->emplace_back(std::move(newVProp)); + } + } + } else { + node->markDeleted(); + status_ = depsPruneProperties(node->dependencies()); + return; + } + node->setVertexProps(std::move(prunedVertexProps)); + } + + status_ = depsPruneProperties(node->dependencies()); +} + +void PrunePropertiesVisitor::visit(BiJoin *node) { + for (auto *hashKey : node->hashKeys()) { + status_ = extractPropsFromExpr(hashKey); + if (!status_.ok()) { + return; + } + } + for (auto *probeKey : node->probeKeys()) { + status_ = extractPropsFromExpr(probeKey); + if (!status_.ok()) { + return; + } + } + status_ = depsPruneProperties(node->dependencies()); +} + +Status PrunePropertiesVisitor::depsPruneProperties(std::vector &dependencies) { + for (const auto *dep : dependencies) { + const_cast(dep)->accept(this); + } + return Status::OK(); +} + +Status PrunePropertiesVisitor::extractPropsFromExpr(const Expression *expr, + const std::string &entityAlias) { + PropertyTrackerVisitor visitor(qctx_, spaceID_, propsUsed_, entityAlias); + const_cast(expr)->accept(&visitor); + if (!visitor.ok()) { + return visitor.status(); + } + + return Status::OK(); +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/visitor/PrunePropertiesVisitor.h b/src/graph/visitor/PrunePropertiesVisitor.h new file mode 100644 index 00000000000..a0a04c8ee50 --- /dev/null +++ b/src/graph/visitor/PrunePropertiesVisitor.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#ifndef GRAPH_VISITOR_PRUNEPROPERTIES_VISITOR_H_ +#define GRAPH_VISITOR_PRUNEPROPERTIES_VISITOR_H_ + +#include "graph/planner/plan/PlanNode.h" +#include "graph/planner/plan/PlanNodeVisitor.h" +#include "graph/planner/plan/Query.h" +#include "graph/visitor/PropertyTrackerVisitor.h" + +namespace nebula { +namespace graph { + +class PrunePropertiesVisitor final : public PlanNodeVisitor { + public: + PrunePropertiesVisitor(PropertyTracker &propsUsed, + graph::QueryContext *qctx, + GraphSpaceID spaceID); + + bool ok() const { + return status_.ok(); + } + + const Status &status() const { + return status_; + } + + void visit(PlanNode *node) override; + void visit(Filter *node) override; + void visit(Project *node) override; + void visit(Aggregate *node) override; + void visit(Traverse *node) override; + void visit(AppendVertices *node) override; + void visit(BiJoin *node) override; + + private: + Status depsPruneProperties(std::vector &dependencies); + Status extractPropsFromExpr(const Expression *expr, const std::string &entityAlias = ""); + + PropertyTracker &propsUsed_; + QueryContext *qctx_; + GraphSpaceID spaceID_; + Status status_; +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_VISITOR_PRUNEPROPERTIES_VISITOR_H_ diff --git a/tests/tck/features/optimizer/PrunePropertiesRule.feature b/tests/tck/features/optimizer/PrunePropertiesRule.feature new file mode 100644 index 00000000000..55f4455c4c1 --- /dev/null +++ b/tests/tck/features/optimizer/PrunePropertiesRule.feature @@ -0,0 +1,364 @@ +# Copyright (c) 2021 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: Prune Properties rule + + Background: + Given a graph with space named "nba" + + Scenario: Single Match + When profiling query: + """ + MATCH p = (v:player{name: "Tony Parker"})-[e:like]->(v2) + RETURN v2.player.age + """ + Then the result should be, in order: + | v2.player.age | + | 42 | + | 33 | + | 41 | + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 8 | Project | 4 | | + # | 4 | AppendVertices | 3 | { "props": "[{\"props\":[\"age\"],\"tagId\":2}]" } | + # | 3 | Traverse | 7 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":2}]", "edgeProps": "[{\"props\":[\"_dst\", \"_rank\", \"_type\", \"_src\"],\"type\":5}]" } | + # | 7 | IndexScan | 2 | | + # | 2 | Start | | | + When profiling query: + """ + MATCH p = (v:player{name: "Tony Parker"})-[e:like]->(v2) + RETURN v.player.name + """ + Then the result should be, in order: + | v.player.name | + | "Tony Parker" | + | "Tony Parker" | + | "Tony Parker" | + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 8 | Project | 4 | | + # | 4 | AppendVertices | 3 | { "props": } | + # | 3 | Traverse | 7 | { "vertexProps": "[{\"props\":[\"name\", \"age\"],\"tagId\":2}]", "edgeProps": "[{\"props\":[\"_dst\", \"_rank\", \"_type\", \"_src\"],\"type\":5}]" } | + # | 7 | IndexScan | 2 | | + # | 2 | Start | | | + When profiling query: + """ + MATCH p = (v:player{name: "Tony Parker"})-[e:like]-(v2) + RETURN v + """ + Then the result should be, in order: + | v | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 8 | Project | 4 | | + # | 4 | AppendVertices | 3 | { "props": } | + # | 3 | Traverse | 7 | { "vertexProps": "[{\"props\":[\"name\", \"age\"],\"tagId\":2}]", "edgeProps": "[{\"props\":[\"_dst\", \"_rank\", \"_type\", \"_src\"],\"type\":5}]" } | + # | 7 | IndexScan | 2 | | + # | 2 | Start | | | + When profiling query: + """ + MATCH p = (v:player{name: "Tony Parker"})-[e:like]->(v2) + RETURN v2 + """ + Then the result should be, in order: + | v2 | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"}) | + | ("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"}) | + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 8 | Project | 4 | | + # | 4 | AppendVertices | 3 | { "props": "[{\"props\":[\"name\", \"_tag\"],\"tagId\":3}, {\"props\":[\"name\", \"name\", \"age\", \"_tag\"],\"tagId\":2}, {\"props\":[\"name\", \"speciality\", \"_tag\"], \"tagId\":4}]" } | + # | 3 | Traverse | 7 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":2}]", "edgeProps": "[{\"props\":[\"_dst\", \"_rank\", \"_type\", \"_src\"],\"type\":5}]" } | + # | 7 | IndexScan | 2 | | + # | 2 | Start | | | + # The rule will not take affect in this case because it returns the whole path + When executing query: + """ + MATCH p = (v:player{name: "Tony Parker"})-[e:like]-(v2) + RETURN p + """ + Then the result should be, in order: + | p | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})<-[:like@0 {likeness: 99}]-("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})<-[:like@0 {likeness: 80}]-("Boris Diaw" :player{age: 36, name: "Boris Diaw"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})<-[:like@0 {likeness: 75}]-("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 90}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})<-[:like@0 {likeness: 95}]-("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})<-[:like@0 {likeness: 50}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})> | + When profiling query: + """ + MATCH p = (v:player{name: "Tony Parker"})-[e:like]->(v2) + RETURN type(e) + """ + Then the result should be, in order: + | type(e) | + | "like" | + | "like" | + | "like" | + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 8 | Project | 4 | | + # | 4 | AppendVertices | 3 | { "props": } | + # | 3 | Traverse | 7 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":2}]", "edgeProps": "[{\"props\":[\"_dst\", \"_rank\", \"_type\", \"_src\"],\"type\":5}]" } | + # | 7 | IndexScan | 2 | | + # | 2 | Start | | | + When executing query: + """ + MATCH (v:player{name: "Tony Parker"})-[:like]-(v2)--(v3) + WITH v3, v3.player.age AS age + RETURN v3, age ORDER BY age LIMIT 3 + """ + Then the result should be, in order: + | v3 | age | + | ("Kyle Anderson" :player{age: 25, name: "Kyle Anderson"}) | 25 | + | ("Damian Lillard" :player{age: 28, name: "Damian Lillard"}) | 28 | + | ("Damian Lillard" :player{age: 28, name: "Damian Lillard"}) | 28 | + + Scenario: Multi Path Patterns + When profiling query: + """ + MATCH (m)-[]-(n), (n)-[]-(l) WHERE id(m)=="Tim Duncan" + RETURN m.player.name AS n1, n.player.name AS n2, + CASE WHEN l.team.name is not null THEN l.team.name + WHEN l.player.name is not null THEN l.player.name ELSE "null" END AS n3 ORDER BY n1, n2, n3 LIMIT 10 + """ + Then the result should be, in order: + | n1 | n2 | n3 | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | + | "Tim Duncan" | "Aron Baynes" | "Pistons" | + | "Tim Duncan" | "Aron Baynes" | "Spurs" | + | "Tim Duncan" | "Aron Baynes" | "Tim Duncan" | + | "Tim Duncan" | "Boris Diaw" | "Hawks" | + | "Tim Duncan" | "Boris Diaw" | "Hornets" | + | "Tim Duncan" | "Boris Diaw" | "Jazz" | + | "Tim Duncan" | "Boris Diaw" | "Spurs" | + | "Tim Duncan" | "Boris Diaw" | "Suns" | + | "Tim Duncan" | "Boris Diaw" | "Tim Duncan" | + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 15 | DataCollect | 16 | | + # | 16 | TopN | 12 | | + # | 12 | Project | 18 | | + # | 18 | Project | 17 | | + # | 17 | Filter | 9 | | + # | 9 | BiInnerJoin | 5, 8 | | + # | 5 | AppendVertices | 4 | { "props": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 4 | Traverse | 2 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 2 | Dedup | 1 | | + # | 1 | PassThrough | 3 | | + # | 3 | Start | | | + # | 8 | AppendVertices | 7 | { "props": "[{\"tagId\":2,\"props\":[\"name\"]}, {\"tagId\":3,\"props\":[\"name\"]}]" } | + # | 7 | Traverse | 6 | { "vertexProps": "[{\"tagId\":2,\"props\":[\"name\"]}]" } | + # | 6 | Argument | | | + When profiling query: + """ + MATCH (m)-[]-(n), (n)-[]-(l), (l)-[]-(h) WHERE id(m)=="Tim Duncan" + RETURN m.player.name AS n1, n.player.name AS n2, l.team.name AS n3, h.player.name AS n4 + ORDER BY n1, n2, n3, n4 LIMIT 10 + """ + Then the result should be, in order: + | n1 | n2 | n3 | n4 | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | "Aron Baynes" | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | "Kyrie Irving" | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | "Rajon Rondo" | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | "Ray Allen" | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | "Shaquile O'Neal" | + | "Tim Duncan" | "Aron Baynes" | "Pistons" | "Aron Baynes" | + | "Tim Duncan" | "Aron Baynes" | "Pistons" | "Blake Griffin" | + | "Tim Duncan" | "Aron Baynes" | "Pistons" | "Grant Hill" | + | "Tim Duncan" | "Aron Baynes" | "Spurs" | "Aron Baynes" | + | "Tim Duncan" | "Aron Baynes" | "Spurs" | "Boris Diaw" | + + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 19 | DataCollect | 20 | | + # | 20 | TopN | 23 | | + # | 23 | Project | 21 | | + # | 21 | Filter | 13 | | + # | 13 | BiInnerJoin | 9, 12 | | + # | 9 | BiInnerJoin | 5, 8 | | + # | 5 | AppendVertices | 4 | { "props": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 4 | Traverse | 2 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 2 | Dedup | 1 | | + # | 1 | PassThrough | 3 | | + # | 3 | Start | | | + # | 8 | AppendVertices | 7 | { "props": "[{\"tagId\":2,\"props\":[\"name\"]}, {\"tagId\":3,\"props\":[\"name\"]}]" } | + # | 7 | Traverse | 6 | { "vertexProps": "[{\"tagId\":2,\"props\":[\"name\"]}]" } | + # | 6 | Argument | | | + # | 12 | AppendVertices | 11 | { "props": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 11 | Traverse | 10 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":3}]" } | + # | 10 | Argument | | | + Scenario: Multi Match + When profiling query: + """ + MATCH (m)-[]-(n) WHERE id(m)=="Tim Duncan" + MATCH (n)-[]-(l) + RETURN m.player.name AS n1, n.player.name AS n2, + CASE WHEN l.player.name is not null THEN l.player.name + WHEN l.team.name is not null THEN l.team.name ELSE "null" END AS n3 ORDER BY n1, n2, n3 LIMIT 10 + """ + Then the result should be, in order: + | n1 | n2 | n3 | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | + | "Tim Duncan" | "Aron Baynes" | "Pistons" | + | "Tim Duncan" | "Aron Baynes" | "Spurs" | + | "Tim Duncan" | "Aron Baynes" | "Tim Duncan" | + | "Tim Duncan" | "Boris Diaw" | "Hawks" | + | "Tim Duncan" | "Boris Diaw" | "Hornets" | + | "Tim Duncan" | "Boris Diaw" | "Jazz" | + | "Tim Duncan" | "Boris Diaw" | "Spurs" | + | "Tim Duncan" | "Boris Diaw" | "Suns" | + | "Tim Duncan" | "Boris Diaw" | "Tim Duncan" | + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 16 | DataCollect | 17 | | + # | 17 | TopN | 13 | | + # | 13 | Project | 12 | | + # | 12 | BiInnerJoin | 19, 11 | | + # | 19 | Project | 18 | | + # | 18 | Filter | 5 | | + # | 5 | AppendVertices | 4 | { "props": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 4 | Traverse | 2 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 2 | Dedup | 1 | | + # | 1 | PassThrough | 3 | | + # | 3 | Start | | | + # | 11 | Project | 10 | | + # | 10 | AppendVertices | 9 | { "props": "[{\"tagId\":2,\"props\":[\"name\"]}, {\"tagId\":3,\"props\":[\"name\"]}]" } | + # | 9 | Traverse | 8 | { "vertexProps": "[{\"tagId\":2,\"props\":[\"name\"]}]" } | + # | 8 | Argument | | | + When profiling query: + """ + MATCH (m)-[]-(n) WHERE id(m)=="Tim Duncan" + MATCH (n)-[]-(l), (l)-[]-(h) + RETURN m.player.name AS n1, n.player.name AS n2, l.team.name AS n3, h.player.name AS n4 + ORDER BY n1, n2, n3, n4 LIMIT 10 + """ + Then the result should be, in order: + | n1 | n2 | n3 | n4 | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | "Aron Baynes" | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | "Kyrie Irving" | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | "Rajon Rondo" | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | "Ray Allen" | + | "Tim Duncan" | "Aron Baynes" | "Celtics" | "Shaquile O'Neal" | + | "Tim Duncan" | "Aron Baynes" | "Pistons" | "Aron Baynes" | + | "Tim Duncan" | "Aron Baynes" | "Pistons" | "Blake Griffin" | + | "Tim Duncan" | "Aron Baynes" | "Pistons" | "Grant Hill" | + | "Tim Duncan" | "Aron Baynes" | "Spurs" | "Aron Baynes" | + | "Tim Duncan" | "Aron Baynes" | "Spurs" | "Boris Diaw" | + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 20 | DataCollect | 21 | | + # | 21 | TopN | 17 | | + # | 17 | Project | 16 | | + # | 16 | BiInnerJoin | 23, 15 | | + # | 23 | Project | 22 | | + # | 22 | Filter | 5 | | + # | 5 | AppendVertices | 4 | { "props": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 4 | Traverse | 2 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 2 | Dedup | 1 | | + # | 1 | PassThrough | 3 | | + # | 3 | Start | | | + # | 15 | Project | 14 | | + # | 14 | BiInnerJoin | 10, 13 | | + # | 10 | AppendVertices | 9 | { "props": "[{\"tagId\":2,\"props\":[\"name\"]}, {\"tagId\":3,\"props\":[\"name\"]}]" } | + # | 9 | Traverse | 8 | { "vertexProps": "[{\"tagId\":2,\"props\":[\"name\"]}]" } | + # | 8 | Argument | | | + # | 13 | AppendVertices | 12 | { "props": "[{\"tagId\":2,\"props\":[\"name\"]}, {\"tagId\":3,\"props\":[\"name\"]}]" } | + # | 12 | Traverse | 11 | { "vertexProps": "[{\"tagId\":3,\"props\":[\"name\"]}]" } | + # | 11 | Argument | | | + When profiling query: + """ + MATCH (v:player{name:"Tony Parker"}) + WITH v AS a + MATCH p=(o:player{name:"Tim Duncan"})-[]->(a) + RETURN o.player.name + """ + Then the result should be, in order: + | o.player.name | + | "Tim Duncan" | + | "Tim Duncan" | + + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 10 | Project | 11 | | + # | 11 | BiInnerJoin | 14, 9 | | + # | 14 | Project | 3 | | + # | 3 | AppendVertices | 12 | { "props": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 12 | IndexScan | 2 | | + # | 2 | Start | | | + # | 9 | Project | 8 | | + # | 8 | AppendVertices | 7 | { "props": "[{\"props\":[\"name\", \"_tag\"],\"tagId\":3}, {\"props\":[\"name\", \"age\", \"_tag\"],\"tagId\":2}, {\"props\":[\"name\", , \"speciality\", \"_tag\"],\"tagId\":4}, {\"props\":[\"id\", \"ts\", \"_tag\"],\"tagId\":6}]" } | + # | 7 | Traverse | 6 | { "vertexProps": "[{\"props\":[\"name\", \"_tag\"],\"tagId\":3}, {\"props\":[\"name\", \"age\", \"_tag\"],\"tagId\":2}, {\"props\":[\"name\", , \"speciality\", \"_tag\"],\"tagId\":4}, {\"props\":[\"id\", \"ts\", \"_tag\"],\"tagId\":6}]" } | + # | 6 | Argument | | | + Scenario: Optional Match + When profiling query: + """ + MATCH (m)-[]-(n) WHERE id(m)=="Tim Duncan" + OPTIONAL MATCH (n)<-[:serve]-(l) + RETURN m.player.name AS n1, n.player.name AS n2, l AS n3 ORDER BY n1, n2, n3 LIMIT 10 + """ + Then the result should be, in order: + | n1 | n2 | n3 | + | "Tim Duncan" | "Aron Baynes" | NULL | + | "Tim Duncan" | "Boris Diaw" | NULL | + | "Tim Duncan" | "Danny Green" | NULL | + | "Tim Duncan" | "Danny Green" | NULL | + | "Tim Duncan" | "Dejounte Murray" | NULL | + | "Tim Duncan" | "LaMarcus Aldridge" | NULL | + | "Tim Duncan" | "LaMarcus Aldridge" | NULL | + | "Tim Duncan" | "Manu Ginobili" | NULL | + | "Tim Duncan" | "Manu Ginobili" | NULL | + | "Tim Duncan" | "Manu Ginobili" | NULL | + # And the execution plan should be: + # | id | name | dependencies | operator info | + # | 16 | DataCollect | 17 | | + # | 17 | TopN | 13 | | + # | 13 | Project | 12 | | + # | 12 | BiLeftJoin | 19, 11 | | + # | 19 | Project | 18 | | + # | 18 | Filter | 5 | | + # | 5 | AppendVertices | 4 | { "props": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 4 | Traverse | 2 | | + # | 2 | Dedup | 1 | | + # | 1 | PassThrough | 3 | | + # | 3 | Start | | | + # | 11 | Project | 10 | | + # | 10 | AppendVertices | 9 | { "props": "[{\"props\":[\"name\", \"_tag\"],\"tagId\":3}, {\"props\":[\"name\", \"age\", \"_tag\"],\"tagId\":2}, {\"props\":[\"name\", , \"speciality\", \"_tag\"],\"tagId\":4}, {\"props\":[\"id\", \"ts\", \"_tag\"],\"tagId\":6}]" } | + # | 9 | Traverse | 8 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":2}]" } | + # | 8 | Argument | | | + When profiling query: + """ + MATCH (m:player{name:"Tim Duncan"})-[:like]-(n)--() + WITH m,n + MATCH (m)--(n) + RETURN count(*) AS scount + """ + Then the result should be, in order: + | scount | + | 270 | + +# And the execution plan should be: +# | id | name | dependencies | operator info | +# | 12 | Aggregate | 13 | | +# | 13 | BiInnerJoin | 15, 11 | | +# | 15 | Project | 5 | | +# | 5 | AppendVertices | 4 | { "props": "[]" } | +# | 4 | Traverse | 3 | { "vertexProps": "" } | +# | 3 | Traverse | 14 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":2}]" } | +# | 14 | IndexScan | 2 | | +# | 2 | Start | | | +# | 11 | Project | 10 | | +# | 10 | AppendVertices | 9 | { "props": "[]" } | +# | 9 | Traverse | 8 | { "vertexProps": "[{\"props\":[\"name\"],\"tagId\":2}]" } | +# | 8 | Argument | | |