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

push filter down traverse rule #4987

Merged
merged 3 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion src/common/expression/PredicateExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ std::unordered_map<std::string, PredicateExpression::Type> PredicateExpression::
const Value& PredicateExpression::evalExists(ExpressionContext& ctx) {
DCHECK(collection_->kind() == Expression::Kind::kAttribute ||
collection_->kind() == Expression::Kind::kSubscript ||
collection_->kind() == Expression::Kind::kLabelTagProperty);
collection_->kind() == Expression::Kind::kLabelTagProperty)
<< "actual kind: " << collection_->kind() << ", toString: " << toString();

if (collection_->kind() == Expression::Kind::kLabelTagProperty) {
result_ = !collection_->eval(ctx).isNull();
Expand Down
1 change: 1 addition & 0 deletions src/graph/optimizer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ nebula_add_library(
rule/EliminateAppendVerticesRule.cpp
rule/PushLimitDownScanEdgesRule.cpp
rule/RemoveProjectDedupBeforeGetDstBySrcRule.cpp
rule/PushFilterDownTraverseRule.cpp
)

nebula_add_subdirectory(test)
4 changes: 3 additions & 1 deletion src/graph/optimizer/OptGroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ void OptGroup::addGroupNode(OptGroupNode *groupNode) {
if (outputVar_.empty()) {
outputVar_ = groupNode->node()->outputVar();
} else {
DCHECK_EQ(outputVar_, groupNode->node()->outputVar());
DCHECK_EQ(outputVar_, groupNode->node()->outputVar())
<< "outputVar_: " << outputVar_ << "groupNode plan: " << groupNode->node()->toString()
<< ", groupNode->node()->outputVar(): " << groupNode->node()->outputVar();
}
groupNodes_.emplace_back(groupNode);
groupNode->node()->updateSymbols();
Expand Down
152 changes: 152 additions & 0 deletions src/graph/optimizer/rule/PushFilterDownTraverseRule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/* Copyright (c) 2022 vesoft inc. All rights reserved.
*
* This source code is licensed under Apache 2.0 License.
*/

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

#include "common/expression/ConstantExpression.h"
#include "common/expression/Expression.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"
#include "graph/visitor/ExtractFilterExprVisitor.h"

using nebula::Expression;
using nebula::graph::Filter;
using nebula::graph::PlanNode;
using nebula::graph::QueryContext;
using nebula::graph::Traverse;

namespace nebula {
namespace opt {

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

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

const Pattern& PushFilterDownTraverseRule::pattern() const {
static Pattern pattern =
Pattern::create(PlanNode::Kind::kFilter,
{Pattern::create(PlanNode::Kind::kAppendVertices,
{Pattern::create(PlanNode::Kind::kTraverse)})});
return pattern;
jievince marked this conversation as resolved.
Show resolved Hide resolved
}

bool PushFilterDownTraverseRule::match(OptContext* ctx, const MatchedResult& matched) const {
if (!OptRule::match(ctx, matched)) {
return false;
}
DCHECK_EQ(matched.dependencies[0].dependencies[0].node->node()->kind(),
PlanNode::Kind::kTraverse);
jievince marked this conversation as resolved.
Show resolved Hide resolved
auto traverse =
static_cast<const Traverse*>(matched.dependencies[0].dependencies[0].node->node());
return traverse->isOneStep();
}

StatusOr<OptRule::TransformResult> PushFilterDownTraverseRule::transform(
OptContext* ctx, const MatchedResult& matched) const {
auto* filterGroupNode = matched.node;
auto* filterGroup = filterGroupNode->group();
auto* filter = static_cast<graph::Filter*>(filterGroupNode->node());
auto* condition = filter->condition();

auto* avGroupNode = matched.dependencies[0].node;
auto* av = static_cast<graph::AppendVertices*>(avGroupNode->node());

auto* tvGroupNode = matched.dependencies[0].dependencies[0].node;
auto* tv = static_cast<graph::Traverse*>(tvGroupNode->node());
auto& edgeAlias = tv->edgeAlias();

auto qctx = ctx->qctx();
auto pool = qctx->objPool();

// Pick the expr looks like `$-.e[0].likeness
auto picker = [&edgeAlias](const Expression* e) -> bool {
// TODO(jie): Handle the strange exists expr. e.g. exists(e.likeness)
auto exprs = graph::ExpressionUtils::collectAll(e, {Expression::Kind::kPredicate});
for (auto* expr : exprs) {
if (static_cast<const PredicateExpression*>(expr)->name() == "exists") {
return false;
}
}

auto varProps = graph::ExpressionUtils::collectAll(
e, {Expression::Kind::kInputProperty, Expression::Kind::kVarProperty});
if (varProps.empty()) {
return false;
}
for (auto* expr : varProps) {
DCHECK(graph::ExpressionUtils::isPropertyExpr(expr));
auto& propName = static_cast<const PropertyExpression*>(expr)->prop();
if (propName != edgeAlias) return false;
}
return true;
};
Expression* filterPicked = nullptr;
Expression* filterUnpicked = nullptr;
graph::ExpressionUtils::splitFilter(condition, picker, &filterPicked, &filterUnpicked);

if (!filterPicked) {
return TransformResult::noTransform();
}
auto* newFilterPicked =
graph::ExpressionUtils::rewriteEdgePropertyFilter(pool, edgeAlias, filterPicked->clone());

Filter* newFilter = nullptr;
OptGroupNode* newFilterGroupNode = nullptr;
if (filterUnpicked) {
newFilter = Filter::make(qctx, nullptr, filterUnpicked);
newFilter->setOutputVar(filter->outputVar());
newFilter->setColNames(filter->colNames());
newFilterGroupNode = OptGroupNode::create(ctx, newFilter, filterGroup);
}

auto* newAv = static_cast<graph::AppendVertices*>(av->clone());

OptGroupNode* newAvGroupNode = nullptr;
if (newFilterGroupNode) {
auto* newAvGroup = OptGroup::create(ctx);
newAvGroupNode = newAvGroup->makeGroupNode(newAv);
newFilterGroupNode->dependsOn(newAvGroup);
newFilter->setInputVar(newAv->outputVar());
} else {
newAvGroupNode = OptGroupNode::create(ctx, newAv, filterGroup);
newAv->setOutputVar(filter->outputVar());
}

auto* eFilter = tv->eFilter();
Expression* newEFilter = eFilter
? LogicalExpression::makeAnd(pool, newFilterPicked, eFilter->clone())
: newFilterPicked;

auto* newTv = static_cast<graph::Traverse*>(tv->clone());
newAv->setInputVar(newTv->outputVar());
newTv->setEdgeFilter(newEFilter);

auto* newTvGroup = OptGroup::create(ctx);
newAvGroupNode->dependsOn(newTvGroup);
auto* newTvGroupNode = newTvGroup->makeGroupNode(newTv);

for (auto dep : tvGroupNode->dependencies()) {
newTvGroupNode->dependsOn(dep);
}

TransformResult result;
result.eraseCurr = true;
result.newGroupNodes.emplace_back(newFilterGroupNode ? newFilterGroupNode : newAvGroupNode);

return result;
}

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

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

#pragma once

#include "graph/optimizer/OptRule.h"

namespace nebula {
namespace opt {

/*
* Before:
* Filter(e.likeness > 78)
* |
* AppendVertices
* |
* Traverse
*
* After :
* AppendVertices
* |
* Traverse(eFilter_: *.likeness > 78)
*/
class PushFilterDownTraverseRule final : public OptRule {
public:
const Pattern &pattern() const override;

bool match(OptContext *ctx, const MatchedResult &matched) const override;

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

std::string toString() const override;

private:
PushFilterDownTraverseRule();

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

} // namespace opt
} // namespace nebula
20 changes: 20 additions & 0 deletions src/graph/planner/plan/Query.h
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,10 @@ class Traverse final : public GetNeighbors {
return range_;
}

bool isOneStep() const {
return !range_;
}

// Contains zero step
bool zeroStep() const {
return range_ != nullptr && range_->min() == 0;
Expand All @@ -1555,6 +1559,17 @@ class Traverse final : public GetNeighbors {
return trackPrevPath_;
}

const std::string& nodeAlias() const {
auto& cols = this->colNames();
DCHECK_GE(cols.size(), 2);
return cols[cols.size() - 2];
}

const std::string& edgeAlias() const {
DCHECK(!this->colNames().empty());
return this->colNames().back();
}

void setStepRange(MatchStepRange* range) {
range_ = range;
}
Expand Down Expand Up @@ -1618,6 +1633,11 @@ class AppendVertices final : public GetVertices {
return trackPrevPath_;
}

const std::string nodeAlias() const {
DCHECK(!this->colNames().empty());
return this->colNames().back();
}

void setVertexFilter(Expression* vFilter) {
vFilter_ = vFilter;
}
Expand Down
63 changes: 63 additions & 0 deletions src/graph/util/ExpressionUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ Expression *ExpressionUtils::rewriteInExpr(const Expression *expr) {
return const_cast<Expression *>(expr);
}
std::vector<Expression *> operands;
operands.reserve(values.size());
for (const auto &v : values) {
operands.emplace_back(
RelationalExpression::makeEQ(pool, inExpr->left(), ConstantExpression::make(pool, v)));
Expand Down Expand Up @@ -1431,5 +1432,67 @@ bool ExpressionUtils::checkExprDepth(const Expression *expr) {
return true;
}

/*static*/
bool ExpressionUtils::isSingleLenExpandExpr(const std::string &edgeAlias, const Expression *expr) {
if (expr->kind() != Expression::Kind::kAttribute) {
return false;
}
auto attributeExpr = static_cast<const AttributeExpression *>(expr);
auto *left = attributeExpr->left();
auto *right = attributeExpr->right();

if (left->kind() != Expression::Kind::kSubscript) return false;
if (right->kind() != Expression::Kind::kConstant ||
!static_cast<const ConstantExpression *>(right)->value().isStr())
return false;

auto subscriptExpr = static_cast<const SubscriptExpression *>(left);
auto *listExpr = subscriptExpr->left();
auto *idxExpr = subscriptExpr->right();
if (listExpr->kind() != Expression::Kind::kInputProperty &&
listExpr->kind() != Expression::Kind::kVarProperty) {
return false;
}
if (static_cast<const PropertyExpression *>(listExpr)->prop() != edgeAlias) {
return false;
}

// NOTE(jie): Just handled `$-.e[0].likeness` for now, whileas the traverse is single length
// expand.
// TODO(jie): Handle `ALL(i IN e WHERE i.likeness > 78)`, whileas the traverse is var len
// expand.
if (idxExpr->kind() != Expression::Kind::kConstant ||
static_cast<const ConstantExpression *>(idxExpr)->value() != 0) {
return false;
}
return true;
}

// Transform expression `$-.e[0].likeness` to EdgePropertyExpression `like.likeness`
// for more friendly to push down
// \param pool object pool to hold ownership of objects alloacted
// \param edgeAlias the name of edge. e.g. e in pattern -[e]->
// \param expr the filter expression
/*static*/ Expression *ExpressionUtils::rewriteEdgePropertyFilter(ObjectPool *pool,
const std::string &edgeAlias,
Expression *expr) {
graph::RewriteVisitor::Matcher matcher = [&edgeAlias](const Expression *e) -> bool {
return isSingleLenExpandExpr(edgeAlias, e);
};
graph::RewriteVisitor::Rewriter rewriter = [pool](const Expression *e) -> Expression * {
DCHECK_EQ(e->kind(), Expression::Kind::kAttribute);
auto attributeExpr = static_cast<const AttributeExpression *>(e);
auto *right = attributeExpr->right();
DCHECK_EQ(right->kind(), Expression::Kind::kConstant);

auto &prop = static_cast<const ConstantExpression *>(right)->value().getStr();

auto *edgePropExpr = EdgePropertyExpression::make(pool, "*", prop);
return edgePropExpr;
};

return graph::RewriteVisitor::transform(expr, matcher, rewriter);
}

} // namespace graph
} // namespace nebula
7 changes: 7 additions & 0 deletions src/graph/util/ExpressionUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,13 @@ class ExpressionUtils {
// Whether the whole expression is vertex id predication
// e.g. id(v) == 1, id(v) IN [...]
static bool isVidPredication(const Expression* expr);

// Check if the expr looks like `$-.e[0].likeness`
static bool isSingleLenExpandExpr(const std::string& edgeAlias, const Expression* expr);

static Expression* rewriteEdgePropertyFilter(ObjectPool* pool,
const std::string& edgeAlias,
Expression* expr);
};

} // namespace graph
Expand Down
8 changes: 5 additions & 3 deletions src/storage/query/QueryBaseProcessor-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,11 @@ nebula::cpp2::ErrorCode QueryBaseProcessor<REQ, RESP>::checkExp(
if (ret != nebula::cpp2::ErrorCode::SUCCEEDED) {
return ret;
}
ret = checkExp(predExp->filter(), returned, filtered, updated, allowNoexistentProp);
if (ret != nebula::cpp2::ErrorCode::SUCCEEDED) {
return ret;
if (predExp->hasFilter()) {
ret = checkExp(predExp->filter(), returned, filtered, updated, allowNoexistentProp);
if (ret != nebula::cpp2::ErrorCode::SUCCEEDED) {
return ret;
}
}
return nebula::cpp2::ErrorCode::SUCCEEDED;
}
Expand Down
Loading