diff --git a/src/optimizer/CMakeLists.txt b/src/optimizer/CMakeLists.txt index d186bd16ebccd9b328b4f3e30cce576abadc0905..c79e44f3621669bc469115b7bcb6fff9f4ac8318 100644 --- a/src/optimizer/CMakeLists.txt +++ b/src/optimizer/CMakeLists.txt @@ -12,6 +12,7 @@ nebula_add_library( OptRule.cpp OptContext.cpp rule/PushFilterDownGetNbrsRule.cpp + rule/RemoveNoopProjectRule.cpp rule/MergeGetVerticesAndDedupRule.cpp rule/MergeGetVerticesAndProjectRule.cpp rule/MergeGetNbrsAndDedupRule.cpp diff --git a/src/optimizer/OptGroup.h b/src/optimizer/OptGroup.h index bd48a4789cfdaa605c4cc05f5638ead85840118b..cb9f3ea5e53832775451edb256284d37c926934c 100644 --- a/src/optimizer/OptGroup.h +++ b/src/optimizer/OptGroup.h @@ -79,6 +79,10 @@ public: return dependencies_; } + void setDeps(std::vector<OptGroup *> deps) { + dependencies_ = deps; + } + void addBody(OptGroup *body) { bodies_.emplace_back(body); } diff --git a/src/optimizer/rule/RemoveNoopProjectRule.cpp b/src/optimizer/rule/RemoveNoopProjectRule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..85c9904c25329ea9956f34312568a48fad9b3af3 --- /dev/null +++ b/src/optimizer/rule/RemoveNoopProjectRule.cpp @@ -0,0 +1,108 @@ +/* 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 "optimizer/rule/RemoveNoopProjectRule.h" + +#include "optimizer/OptContext.h" +#include "optimizer/OptGroup.h" +#include "planner/PlanNode.h" +#include "planner/Query.h" + +using nebula::graph::PlanNode; +using nebula::graph::QueryContext; + +namespace nebula { +namespace opt { + +std::unique_ptr<OptRule> RemoveNoopProjectRule::kInstance = + std::unique_ptr<RemoveNoopProjectRule>(new RemoveNoopProjectRule()); + +RemoveNoopProjectRule::RemoveNoopProjectRule() { + RuleSet::QueryRules().addRule(this); +} + +const Pattern& RemoveNoopProjectRule::pattern() const { + static Pattern pattern = Pattern::create(graph::PlanNode::Kind::kProject); + return pattern; +} + +StatusOr<OptRule::TransformResult> RemoveNoopProjectRule::transform( + OptContext* octx, + const MatchedResult& matched) const { + const auto* projGroupNode = matched.node; + const auto* oldProjNode = projGroupNode->node(); + DCHECK_EQ(oldProjNode->kind(), PlanNode::Kind::kProject); + + TransformResult result; + result.eraseAll = true; + const auto* projGroup = projGroupNode->group(); + const auto depGroups = projGroupNode->dependencies(); + DCHECK_EQ(depGroups.size(), 1); + const auto* depGroup = depGroups.front(); + const auto& groupNodes = depGroup->groupNodes(); + for (auto* groupNode : groupNodes) { + auto* newNode = groupNode->node()->clone(); + const auto& newColNames = newNode->colNames(); + const auto& oldColNames = oldProjNode->colNames(); + auto colsNum = newColNames.size(); + if (colsNum != oldColNames.size()) { + return TransformResult::noTransform(); + } + for (size_t i = 0; i < colsNum; ++i) { + if (newColNames[i].compare(oldColNames[i])) { + return TransformResult::noTransform(); + } + } + newNode->setOutputVar(oldProjNode->outputVar()); + auto* newGroupNode = OptGroupNode::create(octx, newNode, projGroup); + newGroupNode->setDeps(groupNode->dependencies()); + result.newGroupNodes.emplace_back(newGroupNode); + } + + return result; +} + +bool RemoveNoopProjectRule::match(OptContext* octx, const MatchedResult& matched) const { + if (!OptRule::match(octx, matched)) { + return false; + } + + auto* projGroupNode = matched.node; + DCHECK_EQ(projGroupNode->node()->kind(), PlanNode::Kind::kProject); + auto depGroups = projGroupNode->dependencies(); + + // handled in Pattern::match + DCHECK_EQ(depGroups.size(), 1); + auto* node = depGroups.front()->groupNodes().front()->node(); + auto kind = node->kind(); + // disable BiInputNode/SetOp (multi input) + // disable IndexScan/PassThrough (multi output) + if (!node->isSingleInput() || + kind == PlanNode::Kind::kUnion || + kind == PlanNode::Kind::kMinus || + kind == PlanNode::Kind::kIntersect || + kind == PlanNode::Kind::kIndexScan || + kind == PlanNode::Kind::kPassThrough) { + return false; + } + + auto* projNode = static_cast<const graph::Project*>(projGroupNode->node()); + std::vector<YieldColumn*> cols = projNode->columns()->columns(); + for (auto* col : cols) { + if (col->alias() || col->expr()->kind() != Expression::Kind::kVarProperty) { + return false; + } + } + + return true; +} + +std::string RemoveNoopProjectRule::toString() const { + return "RemoveNoopProjectRule"; +} + +} // namespace opt +} // namespace nebula diff --git a/src/optimizer/rule/RemoveNoopProjectRule.h b/src/optimizer/rule/RemoveNoopProjectRule.h new file mode 100644 index 0000000000000000000000000000000000000000..2031754667b0bc8d66941f82246e108b7892bf5e --- /dev/null +++ b/src/optimizer/rule/RemoveNoopProjectRule.h @@ -0,0 +1,37 @@ +/* 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. + */ + +#ifndef OPTIMIZER_RULE_REMOVENOOPPROJECTRULE_H_ +#define OPTIMIZER_RULE_REMOVENOOPPROJECTRULE_H_ + +#include <memory> + +#include "optimizer/OptRule.h" + +namespace nebula { +namespace opt { + +class RemoveNoopProjectRule final : public OptRule { +public: + const Pattern &pattern() const override; + + StatusOr<TransformResult> transform(OptContext *ctx, + const MatchedResult &matched) const override; + + bool match(OptContext *ctx, const MatchedResult &matched) const override; + + std::string toString() const override; + +private: + RemoveNoopProjectRule(); + + static std::unique_ptr<OptRule> kInstance; +}; + +} // namespace opt +} // namespace nebula + +#endif // OPTIMIZER_RULE_REMOVENOOPPROJECTRULE_H_ diff --git a/tests/tck/features/optimizer/PushFilterDownAggregateRule.feature b/tests/tck/features/optimizer/PushFilterDownAggregateRule.feature index 9ca947eb36860770526c655b23fdebb44072ce6c..7ed720a0755c0fcbaac450d780a800b4bc0f4878 100644 --- a/tests/tck/features/optimizer/PushFilterDownAggregateRule.feature +++ b/tests/tck/features/optimizer/PushFilterDownAggregateRule.feature @@ -11,10 +11,12 @@ Feature: Push Filter down Aggregate rule When profiling query: """ MATCH (v:player) - WITH v.age+1 AS age, COUNT(v.age) as count - WHERE age<30 - RETURN age,count - ORDER BY age + WITH + v.age+1 AS age, + COUNT(v.age) as count + WHERE age<30 + RETURN age, count + ORDER BY age """ Then the result should be, in any order: | age | count | @@ -34,14 +36,13 @@ Feature: Push Filter down Aggregate rule And the execution plan should be: | id | name | dependencies | operator info | | 13 | DataCollect | 12 | | - | 12 | Sort | 11 | | - | 11 | Project | 18 | | - | 18 | Aggregate | 17 | | - | 17 | Filter | 8 | | + | 12 | Sort | 19 | | + | 19 | Aggregate | 18 | | + | 18 | Filter | 8 | | | 8 | Filter | 7 | | | 7 | Project | 6 | | | 6 | Project | 5 | | - | 5 | Filter | 16 | | - | 16 | GetVertices | 14 | | + | 5 | Filter | 17 | | + | 17 | GetVertices | 14 | | | 14 | IndexScan | 0 | | | 0 | Start | | | diff --git a/tests/tck/features/optimizer/RemoveUselessProjectRule.feature b/tests/tck/features/optimizer/RemoveUselessProjectRule.feature new file mode 100644 index 0000000000000000000000000000000000000000..bceb55c6ab8e8d468002cc7f56e26676145004fc --- /dev/null +++ b/tests/tck/features/optimizer/RemoveUselessProjectRule.feature @@ -0,0 +1,80 @@ +# 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. +Feature: Remove Useless Project Rule + + Background: + Given a graph with space named "nba" + + Scenario: Remove useless project + When profiling query: + """ + MATCH (v:player) + WITH + v.age+1 AS age, + count(v.age) AS count + RETURN age, count + ORDER BY age, count + """ + Then the result should be, in any order: + | age | count | + | -3 | 1 | + | -2 | 1 | + | -1 | 1 | + | 0 | 1 | + | 1 | 1 | + | 21 | 1 | + | 23 | 1 | + | 24 | 1 | + | 25 | 1 | + | 26 | 2 | + | 27 | 1 | + | 28 | 1 | + | 29 | 3 | + | 30 | 4 | + | 31 | 4 | + | 32 | 3 | + | 33 | 3 | + | 34 | 4 | + | 35 | 4 | + | 37 | 3 | + | 38 | 1 | + | 39 | 3 | + | 40 | 1 | + | 41 | 2 | + | 42 | 1 | + | 43 | 2 | + | 44 | 1 | + | 46 | 2 | + | 47 | 1 | + | 48 | 1 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 12 | DataCollect | 11 | | + | 11 | Sort | 14 | | + | 14 | Aggregate | 8 | | + | 8 | Filter | 7 | | + | 7 | Project | 6 | | + | 6 | Project | 5 | | + | 5 | Filter | 16 | | + | 16 | GetVertices | 13 | | + | 13 | IndexScan | 0 | | + | 0 | Start | | | + When profiling query: + """ + MATCH p = (n:player{name:"Tony Parker"}) + RETURN n, p + """ + Then the result should be, in any order: + | n | p | + | ("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 | + | 11 | Filter | 7 | | + | 7 | Project | 6 | | + | 6 | Project | 5 | | + | 5 | Filter | 13 | | + | 13 | GetVertices | 10 | | + | 10 | IndexScan | 0 | | + | 0 | Start | | |