Skip to content

Commit

Permalink
Fixed regression when edge referred multiple times
Browse files Browse the repository at this point in the history
This should be opt as scanEdge, too:

MATCH ()-[e]->() return e, properties(e) as ep

add: project-> project-> limit -> av->traverse->sv
add: project-> project-> av ->traverse->sv
  • Loading branch information
wey-gu committed Mar 27, 2022
1 parent c80f44d commit 290eb7e
Show file tree
Hide file tree
Showing 6 changed files with 587 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/graph/optimizer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ nebula_add_library(
rule/EliminateRowCollectRule.cpp
rule/PushLimitDownScanAppendVerticesRule.cpp
rule/GetEdgesTransformRule.cpp
rule/GetEdgesTransformDualprojectRule.cpp
rule/GetEdgesTransformLimitRule.cpp
rule/GetEdgesTransformLimitDualprojectRule.cpp
rule/PushLimitDownScanEdgesAppendVerticesRule.cpp
rule/PushTopNDownIndexScanRule.cpp
)
Expand Down
181 changes: 181 additions & 0 deletions src/graph/optimizer/rule/GetEdgesTransformDualprojectRule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/* Copyright (c) 2021 vesoft inc. All rights reserved.
*
* This source code is licensed under Apache 2.0 License.
*/

#include "graph/optimizer/rule/GetEdgesTransformDualprojectRule.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/visitor/ExtractFilterExprVisitor.h"

using nebula::Expression;
using nebula::graph::AppendVertices;
using nebula::graph::PlanNode;
using nebula::graph::Project;
using nebula::graph::QueryContext;
using nebula::graph::ScanEdges;
using nebula::graph::ScanVertices;
using nebula::graph::Traverse;

namespace nebula {
namespace opt {

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

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

const Pattern &GetEdgesTransformDualprojectRule::pattern() const {
static Pattern pattern = Pattern::create(
PlanNode::Kind::kProject,
{Pattern::create(
PlanNode::Kind::kProject,
{Pattern::create(PlanNode::Kind::kAppendVertices,
{Pattern::create(PlanNode::Kind::kTraverse,
{Pattern::create(PlanNode::Kind::kScanVertices)})})})});
return pattern;
}

bool GetEdgesTransformDualprojectRule::match(OptContext *ctx, const MatchedResult &matched) const {
if (!OptRule::match(ctx, matched)) {
return false;
}
auto traverse = static_cast<const Traverse *>(matched.planNode({0, 0, 0, 0}));
auto project = static_cast<const Project *>(matched.planNode({0}));
const auto &colNames = traverse->colNames();
auto colSize = colNames.size();
DCHECK_GE(colSize, 2);
// TODO: Poor readability for optimizer, is there any other way to do the same thing?
if (colNames[colSize - 2][0] != '_') { // src
return false;
}
if (traverse->stepRange() != nullptr) {
return false;
}
for (auto yieldColumn : project->columns()->columns()) {
// exclude p=()-[e]->() return p, e, properties(e) as ep
if (yieldColumn->expr()->kind() == nebula::Expression::Kind::kPathBuild) {
return false;
}
}
return true;
}

StatusOr<OptRule::TransformResult> GetEdgesTransformDualprojectRule::transform(
OptContext *ctx, const MatchedResult &matched) const {
auto projectAboveGroupNode = matched.node;
auto projectAbove = static_cast<const Project *>(projectAboveGroupNode->node());

auto newProjectAbove = projectAbove->clone();
auto newProjectAboveGroupNode =
OptGroupNode::create(ctx, newProjectAbove, projectAboveGroupNode->group());

auto projectBelowGroupNode = matched.dependencies.front().node;
auto projectBelow = static_cast<const Project *>(projectBelowGroupNode->node());

auto newProjectBelow = projectBelow->clone();
auto newProjectBelowGroup = OptGroup::create(ctx);
auto newProjectBelowGroupNode = newProjectBelowGroup->makeGroupNode(newProjectBelow);

newProjectAboveGroupNode->dependsOn(newProjectBelowGroup);

auto appendVerticesGroupNode = matched.dependencies.front().dependencies.front().node;
auto appendVertices = static_cast<const AppendVertices *>(appendVerticesGroupNode->node());
auto traverseGroupNode =
matched.dependencies.front().dependencies.front().dependencies.front().node;
auto traverse = static_cast<const Traverse *>(traverseGroupNode->node());
auto scanVerticesGroupNode = matched.dependencies.front()
.dependencies.front()
.dependencies.front()
.dependencies.front()
.node;
auto qctx = ctx->qctx();

auto newAppendVertices = appendVertices->clone();
auto newAppendVerticesGroup = OptGroup::create(ctx);
auto colSize = appendVertices->colNames().size();
newAppendVertices->setColNames(
{appendVertices->colNames()[colSize - 2], appendVertices->colNames()[colSize - 1]});
auto newAppendVerticesGroupNode = newAppendVerticesGroup->makeGroupNode(newAppendVertices);

newProjectBelowGroupNode->dependsOn(newAppendVerticesGroup);

auto *newScanEdges = traverseToScanEdges(traverse);
if (newScanEdges == nullptr) {
return TransformResult::noTransform();
}
auto newScanEdgesGroup = OptGroup::create(ctx);
auto newScanEdgesGroupNode = newScanEdgesGroup->makeGroupNode(newScanEdges);

auto *newProj = projectEdges(qctx, newScanEdges, traverse->colNames().back());
newProj->setInputVar(newScanEdges->outputVar());
newProj->setOutputVar(traverse->outputVar());
newProj->setColNames({traverse->colNames().back()});
auto newProjGroup = OptGroup::create(ctx);
auto newProjGroupNode = newProjGroup->makeGroupNode(newProj);

newAppendVerticesGroupNode->dependsOn(newProjGroup);
newProjGroupNode->dependsOn(newScanEdgesGroup);
for (auto dep : scanVerticesGroupNode->dependencies()) {
newScanEdgesGroupNode->dependsOn(dep);
}

TransformResult result;
result.eraseCurr = true;
result.newGroupNodes.emplace_back(newProjectAboveGroupNode);
return result;
}

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

/*static*/ graph::ScanEdges *GetEdgesTransformDualprojectRule::traverseToScanEdges(
const graph::Traverse *traverse) {
const auto *edgeProps = traverse->edgeProps();
if (edgeProps == nullptr) {
return nullptr;
}
for (std::size_t i = 0; i < edgeProps->size(); i++) {
auto type = (*edgeProps)[i].get_type();
for (std::size_t j = i + 1; j < edgeProps->size(); j++) {
if (type == -((*edgeProps)[j].get_type())) {
// Don't support to retrieve edges of the inbound/outbound together
return nullptr;
}
}
}
auto scanEdges = ScanEdges::make(
traverse->qctx(),
nullptr,
traverse->space(),
edgeProps == nullptr ? nullptr
: std::make_unique<std::vector<storage::cpp2::EdgeProp>>(*edgeProps),
nullptr,
traverse->dedup(),
traverse->limit(),
{},
traverse->filter() == nullptr ? nullptr : traverse->filter()->clone());
return scanEdges;
}

/*static*/ graph::Project *GetEdgesTransformDualprojectRule::projectEdges(
graph::QueryContext *qctx, PlanNode *input, const std::string &colName) {
auto *yieldColumns = qctx->objPool()->makeAndAdd<YieldColumns>();
auto *edgeExpr = EdgeExpression::make(qctx->objPool());
auto *listEdgeExpr = ListExpression::make(qctx->objPool());
listEdgeExpr->setItems({edgeExpr});
yieldColumns->addColumn(new YieldColumn(listEdgeExpr, colName));
auto project = Project::make(qctx, input, yieldColumns);
project->setColNames({colName});
return project;
}

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

#ifndef GRAPH_OPTIMIZER_RULE_GETEDGESTRANSFORMDUALPROJECTRULE_H
#define GRAPH_OPTIMIZER_RULE_GETEDGESTRANSFORMDUALPROJECTRULE_H

#include "graph/optimizer/OptRule.h"

namespace nebula {

namespace graph {
class ScanEdges;
class Project;
class Traverse;
class PlanNode;
} // namespace graph

namespace opt {

// Convert [[ScanVertices]] to [[ScanEdges]] in certain cases
// Required conditions:
// 1. Match the pattern
// Benefits:
// 1. Avoid doing Traverse to optimize performance
// Quey example:
// 1. match ()-[e]->() return e, properties(e) as ep
//
// Tranformation:
// Before:
// +---------+---------+
// | Project |
// +---------+---------+
// |
// +---------+---------+
// | Project |
// +---------+---------+
// |
// +---------+---------+
// | AppendVertices |
// +---------+---------+
// |
// +---------+---------+
// | Traverse |
// +---------+---------+
// |
// +---------+---------+
// | ScanVertices |
// +---------+---------+
//
// After:
// +---------+---------+
// | Project |
// +---------+---------+
// |
// +---------+---------+
// | Project |
// +---------+---------+
// |
// +---------+---------+
// | AppendVertices |
// +---------+---------+
// |
// +---------+---------+
// | Project |
// +---------+---------+
// |
// +---------+---------+
// | ScanEdges |
// +---------+---------+

class GetEdgesTransformDualprojectRule 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:
GetEdgesTransformDualprojectRule();

static graph::ScanEdges *traverseToScanEdges(const graph::Traverse *traverse);

static graph::Project *projectEdges(graph::QueryContext *qctx,
graph::PlanNode *input,
const std::string &colName);

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

} // namespace opt
} // namespace nebula
#endif

0 comments on commit 290eb7e

Please sign in to comment.