Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add PushFilterDownInnerJoinRule #4599

Merged
merged 5 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/graph/optimizer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ nebula_add_library(
rule/PushFilterDownAggregateRule.cpp
rule/PushFilterDownProjectRule.cpp
rule/PushFilterDownLeftJoinRule.cpp
rule/PushFilterDownInnerJoinRule.cpp
rule/PushFilterDownNodeRule.cpp
rule/PushFilterDownScanVerticesRule.cpp
rule/PushVFilterDownScanVerticesRule.cpp
Expand Down
134 changes: 134 additions & 0 deletions src/graph/optimizer/rule/PushFilterDownInnerJoinRule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/* Copyright (c) 2022 vesoft inc. All rights reserved.
*
* This source code is licensed under Apache 2.0 License.
*/

#include "graph/optimizer/rule/PushFilterDownInnerJoinRule.h"

#include "graph/optimizer/OptContext.h"
#include "graph/optimizer/OptGroup.h"
#include "graph/planner/plan/PlanNode.h"
#include "graph/planner/plan/Query.h"
#include "graph/util/ExpressionUtils.h"

using nebula::graph::PlanNode;
using nebula::graph::QueryContext;

namespace nebula {
namespace opt {

std::unique_ptr<OptRule> PushFilterDownInnerJoinRule::kInstance =
std::unique_ptr<PushFilterDownInnerJoinRule>(new PushFilterDownInnerJoinRule());

PushFilterDownInnerJoinRule::PushFilterDownInnerJoinRule() {
RuleSet::QueryRules().addRule(this);
}

const Pattern& PushFilterDownInnerJoinRule::pattern() const {
static Pattern pattern = Pattern::create(graph::PlanNode::Kind::kFilter,
{Pattern::create(graph::PlanNode::Kind::kInnerJoin)});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's also workable for LeftJoin, Product.

return pattern;
}

StatusOr<OptRule::TransformResult> PushFilterDownInnerJoinRule::transform(
OptContext* octx, const MatchedResult& matched) const {
auto* filterGroupNode = matched.node;
auto* oldFilterNode = filterGroupNode->node();
auto deps = matched.dependencies;
DCHECK_EQ(deps.size(), 1);
auto innerJoinGroupNode = deps.front().node;
auto* innerJoinNode = innerJoinGroupNode->node();
DCHECK_EQ(oldFilterNode->kind(), PlanNode::Kind::kFilter);
DCHECK_EQ(innerJoinNode->kind(), PlanNode::Kind::kInnerJoin);
auto* oldInnerJoinNode = static_cast<graph::InnerJoin*>(innerJoinNode);
const auto* condition = static_cast<graph::Filter*>(oldFilterNode)->condition();
DCHECK(condition);
const std::pair<std::string, int64_t>& leftVar = oldInnerJoinNode->leftVar();
auto symTable = octx->qctx()->symTable();
std::vector<std::string> leftVarColNames = symTable->getVar(leftVar.first)->colNames;

// split the `condition` based on whether the varPropExpr comes from the left
// child
auto picker = [&leftVarColNames](const Expression* e) -> bool {
auto varProps = graph::ExpressionUtils::collectAll(e, {Expression::Kind::kVarProperty});
if (varProps.empty()) {
return false;
}
std::vector<std::string> propNames;
for (auto* expr : varProps) {
DCHECK(expr->kind() == Expression::Kind::kVarProperty);
propNames.emplace_back(static_cast<const VariablePropertyExpression*>(expr)->prop());
}
for (auto prop : propNames) {
auto iter = std::find_if(leftVarColNames.begin(),
leftVarColNames.end(),
[&prop](std::string item) { return !item.compare(prop); });
if (iter == leftVarColNames.end()) {
return false;
}
}
return true;
};
Expression* filterPicked = nullptr;
Expression* filterUnpicked = nullptr;
graph::ExpressionUtils::splitFilter(condition, picker, &filterPicked, &filterUnpicked);

if (!filterPicked) {
return TransformResult::noTransform();
}

// produce new left Filter node
auto* newLeftFilterNode =
graph::Filter::make(octx->qctx(),
const_cast<graph::PlanNode*>(oldInnerJoinNode->dep()),
graph::ExpressionUtils::rewriteInnerVar(filterPicked, leftVar.first));
newLeftFilterNode->setInputVar(leftVar.first);
newLeftFilterNode->setColNames(leftVarColNames);
auto newFilterGroup = OptGroup::create(octx);
auto newFilterGroupNode = newFilterGroup->makeGroupNode(newLeftFilterNode);
for (auto dep : innerJoinGroupNode->dependencies()) {
newFilterGroupNode->dependsOn(dep);
}
auto newLeftFilterOutputVar = newLeftFilterNode->outputVar();

// produce new InnerJoin node
auto* newInnerJoinNode = static_cast<graph::InnerJoin*>(oldInnerJoinNode->clone());
newInnerJoinNode->setLeftVar({newLeftFilterOutputVar, 0});
const std::vector<Expression*>& hashKeys = oldInnerJoinNode->hashKeys();
std::vector<Expression*> newHashKeys;
for (auto* k : hashKeys) {
newHashKeys.emplace_back(graph::ExpressionUtils::rewriteInnerVar(k, newLeftFilterOutputVar));
}
newInnerJoinNode->setHashKeys(newHashKeys);

TransformResult result;
result.eraseAll = true;
if (filterUnpicked) {
auto* newAboveFilterNode = graph::Filter::make(octx->qctx(), newInnerJoinNode);
newAboveFilterNode->setOutputVar(oldFilterNode->outputVar());
newAboveFilterNode->setCondition(filterUnpicked);
auto newAboveFilterGroupNode =
OptGroupNode::create(octx, newAboveFilterNode, filterGroupNode->group());

auto newInnerJoinGroup = OptGroup::create(octx);
auto newInnerJoinGroupNode = newInnerJoinGroup->makeGroupNode(newInnerJoinNode);
newAboveFilterGroupNode->setDeps({newInnerJoinGroup});
newInnerJoinGroupNode->setDeps({newFilterGroup});
result.newGroupNodes.emplace_back(newAboveFilterGroupNode);
} else {
newInnerJoinNode->setOutputVar(oldFilterNode->outputVar());
newInnerJoinNode->setColNames(oldInnerJoinNode->colNames());
auto newInnerJoinGroupNode =
OptGroupNode::create(octx, newInnerJoinNode, filterGroupNode->group());
newInnerJoinGroupNode->setDeps({newFilterGroup});
result.newGroupNodes.emplace_back(newInnerJoinGroupNode);
}
return result;
}

std::string PushFilterDownInnerJoinRule::toString() const {
return "PushFilterDownInnerJoinRule";
}

} // namespace opt
} // namespace nebula
66 changes: 66 additions & 0 deletions src/graph/optimizer/rule/PushFilterDownInnerJoinRule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* Copyright (c) 2022 vesoft inc. All rights reserved.
*
* This source code is licensed under Apache 2.0 License.
*/

#ifndef GRAPH_OPTIMIZER_RULE_PUSHFILTERDOWNINNERJOINRULE_H_
#define GRAPH_OPTIMIZER_RULE_PUSHFILTERDOWNINNERJOINRULE_H_

#include "graph/optimizer/OptRule.h"

namespace nebula {
namespace opt {

// Push down the filter items from the left subplan of [[InnerJoin]]
// Required conditions:
// 1. Match the pattern
// Benefits:
// 1. Filter data early to optimize performance
//
// Tranformation:
// Before:
//
// +-----------+-----------+
// | Filter |
// | ($left>3 and $right<4)|
// +-----------+-----------+
// |
// +------+------+
// | InnerJoin |
// +------+------+
//
// After:
//
// +------+------+
// | Filter |
// | ($right<4) |
// +------+------+
// |
// +------+------+
// | InnerJoin |
// +------+------+
// /
// +------+------+
// | Filter |
// | ($left>3) |
// +------+------+

class PushFilterDownInnerJoinRule final : public OptRule {
public:
const Pattern &pattern() const override;

StatusOr<OptRule::TransformResult> transform(OptContext *qctx,
const MatchedResult &matched) const override;

std::string toString() const override;

private:
PushFilterDownInnerJoinRule();

static std::unique_ptr<OptRule> kInstance;
};

} // namespace opt
} // namespace nebula

#endif // GRAPH_OPTIMIZER_RULE_PUSHFILTERDOWNINNERJOINRULE_H_
31 changes: 31 additions & 0 deletions tests/tck/features/optimizer/PushFilterDownInnerJoinRule.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright (c) 2021 vesoft inc. All rights reserved.
#
# This source code is licensed under Apache 2.0 License.
Feature: Push Filter down InnerJoin rule

Background:
Given a graph with space named "nba"

Scenario: push filter down InnerJoin
When profiling query:
"""
LOOKUP ON player WHERE player.name == "Tony Parker"
YIELD id(vertex) as id |
GO FROM $-.id OVER like
WHERE (like.likeness - 1) >= 0
YIELD like._src AS src_id, like._dst AS dst_id, like.likeness AS likeness
"""
Then the result should be, in any order:
| src_id | dst_id | likeness |
| "Tony Parker" | "LaMarcus Aldridge" | 90 |
| "Tony Parker" | "Manu Ginobili" | 95 |
| "Tony Parker" | "Tim Duncan" | 95 |
And the execution plan should be:
| id | name | dependencies | operator info |
| 10 | Project | 15 | |
| 15 | InnerJoin | 17 | |
| 17 | Project | 18 | |
| 18 | GetNeighbors | 3 | |
| 3 | Project | 11 | |
| 11 | TagIndexPrefixScan | 0 | |
| 0 | Start | | |
22 changes: 11 additions & 11 deletions tests/tck/features/optimizer/PushFilterDownLeftJoinRule.feature
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,23 @@ Feature: Push Filter down LeftJoin rule
| "Tim Duncan" |
And the execution plan should be:
| id | name | dependencies | operator info |
| 24 | Project | 23 | |
| 23 | Filter | 22 | |
| 22 | InnerJoin | 21 | |
| 24 | Project | 34 | |
| 34 | InnerJoin | 33 | |
| 33 | Filter | 21 | |
| 21 | LeftJoin | 20 | |
| 20 | Project | 19 | |
| 19 | GetVertices | 18 | |
| 18 | Project | 31 | |
| 31 | GetNeighbors | 14 | |
| 14 | Project | 13 | |
| 13 | Filter | 12 | |
| 12 | InnerJoin | 11 | |
| 18 | Project | 30 | |
| 30 | GetNeighbors | 14 | |
| 14 | Project | 32 | |
| 32 | InnerJoin | 31 | |
| 31 | Filter | 11 | |
| 11 | LeftJoin | 10 | |
| 10 | Project | 9 | |
| 9 | GetVertices | 8 | |
| 8 | Project | 30 | |
| 30 | GetNeighbors | 27 | |
| 27 | Project | 25 | |
| 8 | Project | 29 | |
| 29 | GetNeighbors | 26 | |
| 26 | Project | 25 | |
| 25 | TagIndexPrefixScan | 0 | |
| 0 | Start | | |
When profiling query:
Expand Down