Skip to content

Commit

Permalink
push filter down traverse rule (#4987)
Browse files Browse the repository at this point in the history
  • Loading branch information
jievince authored Dec 6, 2022
1 parent 2bc6f83 commit f80d5ec
Show file tree
Hide file tree
Showing 11 changed files with 420 additions and 36 deletions.
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;
}

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);
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

0 comments on commit f80d5ec

Please sign in to comment.