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

Support variable when seeking by id in match clause #5486

Merged
merged 4 commits into from
Apr 20, 2023
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
32 changes: 15 additions & 17 deletions src/graph/context/ast/CypherAstContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,40 +230,38 @@ struct CypherContext final : AstContext {
};

struct PatternContext {
explicit PatternContext(PatternKind k) : kind(k) {}
PatternContext(PatternKind k, QueryContext* q, WhereClauseContext* b, GraphSpaceID g)
: kind(k), qctx(q), bindWhereClause(b), spaceId(g) {}

const PatternKind kind;

QueryContext* qctx{nullptr};
WhereClauseContext* bindWhereClause{nullptr};
GraphSpaceID spaceId;

// Output fields
ScanInfo scanInfo;
// initialize start expression in project node
Expression* initialExpr{nullptr};
};

struct NodeContext final : PatternContext {
NodeContext(QueryContext* q, WhereClauseContext* b, GraphSpaceID g, const NodeInfo* i)
: PatternContext(PatternKind::kNode), qctx(q), bindWhereClause(b), spaceId(g), info(i) {}
: PatternContext(PatternKind::kNode, q, b, g), info(i) {}

QueryContext* qctx;
WhereClauseContext* bindWhereClause;
GraphSpaceID spaceId;
const NodeInfo* info;
std::unordered_set<std::string>* nodeAliasesAvailable{nullptr};

// Output fields
ScanInfo scanInfo;
Set ids;
// initialize start expression in project node
Expression* initialExpr{nullptr};
std::string refVarName;
};

struct EdgeContext final : PatternContext {
EdgeContext(QueryContext* q, WhereClauseContext* b, GraphSpaceID g, const EdgeInfo* i)
: PatternContext(PatternKind::kEdge), qctx(q), bindWhereClause(b), spaceId(g), info(i) {}
: PatternContext(PatternKind::kEdge, q, b, g), info(i) {}

QueryContext* qctx;
WhereClauseContext* bindWhereClause;
GraphSpaceID spaceId;
const EdgeInfo* info;

// Output fields
ScanInfo scanInfo;
// initialize start expression in project node
Expression* initialExpr{nullptr};
};

} // namespace graph
Expand Down
46 changes: 30 additions & 16 deletions src/graph/executor/logic/ArgumentExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,46 @@ folly::Future<Status> ArgumentExecutor::execute() {
// MemoryTrackerVerified
auto *argNode = asNode<Argument>(node());
auto &alias = argNode->getAlias();
auto iter = ectx_->getResult(argNode->inputVar()).iter();
DCHECK(iter != nullptr);
auto iter = DCHECK_NOTNULL(ectx_->getResult(argNode->inputVar()).iter());

const auto &successor = successors();
auto kind = (*successor.begin())->node()->kind();
bool flag = (kind != PlanNode::Kind::kGetVertices && kind != PlanNode::Kind::kExpand);
auto sz = iter->size();

DataSet ds;
ds.colNames = argNode->colNames();
ds.rows.reserve(iter->size());
ds.rows.reserve(sz);

VidHashSet unique;
unique.reserve(sz);

auto addRow = [&unique, &ds](const Value &v) {
if (unique.emplace(v).second) {
Row row;
row.values.emplace_back(v);
ds.rows.emplace_back(std::move(row));
}
};

for (; iter->valid(); iter->next()) {
auto &val = iter->getColumn(alias);
if (val.isNull()) {
continue;
}
// TODO(jmq) analyze the type of val in the validation phase
if (flag && !val.isVertex()) {
return Status::Error("Argument only support vertex, but got %s, which is type %s",
val.toString().c_str(),
val.typeName().c_str());
}
if (unique.emplace(val).second) {
Row row;
row.values.emplace_back(val);
ds.rows.emplace_back(std::move(row));

if (argNode->isInputVertexRequired()) {
if (!val.isVertex()) {
return Status::Error("Argument only support vertex, but got %s, whose type is '%s'",
val.toString().c_str(),
val.typeName().c_str());
}
addRow(val);
} else {
if (val.isList()) {
for (auto &v : val.getList().values) {
addRow(v);
}
} else {
addRow(val);
}
}
}
return finish(ResultBuilder().value(Value(std::move(ds))).build());
Expand Down
16 changes: 10 additions & 6 deletions src/graph/optimizer/Optimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@ StatusOr<const PlanNode *> Optimizer::findBestPlan(QueryContext *qctx) {
NG_RETURN_IF_ERROR(doExploration(optCtx.get(), rootGroup));
auto *newRoot = rootGroup->getPlan();

auto status2 = postprocess(const_cast<PlanNode *>(newRoot), qctx, spaceID);
if (!status2.ok()) {
DLOG(ERROR) << "Failed to postprocess plan: " << status2;
}
NG_RETURN_IF_ERROR(postprocess(const_cast<PlanNode *>(newRoot), qctx, spaceID));
return newRoot;
}

Expand All @@ -61,7 +58,8 @@ Status Optimizer::postprocess(PlanNode *root, graph::QueryContext *qctx, GraphSp
graph::PrunePropertiesVisitor visitor(propsUsed, qctx, spaceID);
root->accept(&visitor);
if (!visitor.ok()) {
return visitor.status();
LOG(INFO) << "Failed to prune properties of query plan in post process of optimizer: "
<< visitor.status();
}
}
return Status::OK();
Expand Down Expand Up @@ -175,7 +173,13 @@ Status Optimizer::rewriteArgumentInputVarInternal(PlanNode *root,
case 0: {
if (root->kind() == PlanNode::Kind::kArgument) {
if (!findArgumentRefPlanNodeInPath(path, root) || root->inputVar().empty()) {
return Status::Error("Could not find the right input variable for argument plan node");
DCHECK(!root->outputVarPtr()->colNames.empty());
auto outColumn = root->outputVarPtr()->colNames.back();
return Status::Error(
"Could not generate valid query plan since the argument plan node could not find its "
"input data, please review your query and pay attention to the symbol `%s` usage "
"especially.",
outColumn.c_str());
}
}
break;
Expand Down
1 change: 1 addition & 0 deletions src/graph/planner/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ nebula_add_library(
match/ArgumentFinder.cpp
match/MatchPathPlanner.cpp
match/ShortestPathPlanner.cpp
match/VariableVertexIdSeek.cpp
ngql/PathPlanner.cpp
ngql/GoPlanner.cpp
ngql/SubgraphPlanner.cpp
Expand Down
4 changes: 4 additions & 0 deletions src/graph/planner/PlannersRegister.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "graph/planner/match/PropIndexSeek.h"
#include "graph/planner/match/ScanSeek.h"
#include "graph/planner/match/StartVidFinder.h"
#include "graph/planner/match/VariableVertexIdSeek.h"
#include "graph/planner/match/VertexIdSeek.h"
#include "graph/planner/ngql/FetchEdgesPlanner.h"
#include "graph/planner/ngql/FetchVerticesPlanner.h"
Expand Down Expand Up @@ -99,6 +100,9 @@ void PlannersRegister::registerMatch() {
// MATCH(n:Tag) WHERE n.prop = value RETURN n
startVidFinders.emplace_back(&PropIndexSeek::make);

// WITH 'xxx' AS vid MATCH(n) WHERE id(n)==vid RETURN n
startVidFinders.emplace_back(&VariableVertexIdSeek::make);

// seek by tag or edge(index)
// MATCH(n: tag) RETURN n
// MATCH(s)-[:edge]->(e) RETURN e
Expand Down
25 changes: 19 additions & 6 deletions src/graph/planner/match/StartVidFinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ using StartVidFinderInstantiateFunc = std::function<std::unique_ptr<StartVidFind
// MATCH(n:Tag{prop:value}) RETURN n
// MATCH(n:Tag) WHERE n.Tag.prop = value RETURN n
//
// 4. LabelIndexSeek finds if a plan could traverse from some vids that could be
// 4. VariableVertexIdSeek
// WITH "xx" AS vid MATCH (v:Tag) WHERE id(v)==vid RETURN v
//
// 5. LabelIndexSeek finds if a plan could traverse from some vids that could be
// read from the label indices.
// MATCH(n: tag) RETURN n
// MATCH(s)-[:edge]->(e) RETURN e
//
// 5. ScanSeek finds if a plan could traverse from some vids by scanning.
// 6. ScanSeek finds if a plan could traverse from some vids by scanning.
//
class StartVidFinder {
public:
virtual ~StartVidFinder() = default;
Expand All @@ -49,23 +53,32 @@ class StartVidFinder {

// The derived class should implement matchNode if the finder has
// the ability to find vids from node pattern.
virtual bool matchNode(NodeContext* nodeCtx) = 0;
virtual bool matchNode(NodeContext* /* nodeCtx */) {
return false;
}

// The derived class should implement matchEdge if the finder has
// the ability to find vids from edge pattern.
virtual bool matchEdge(EdgeContext* nodeCtx) = 0;
virtual bool matchEdge(EdgeContext* /* edgeCtx */) {
return false;
}

StatusOr<SubPlan> transform(PatternContext* patternCtx);

virtual StatusOr<SubPlan> transformNode(NodeContext* nodeCtx) = 0;
virtual StatusOr<SubPlan> transformNode(NodeContext* /* nodeCtx */) {
return Status::Error("Unimplemented");
}

virtual StatusOr<SubPlan> transformEdge(EdgeContext* edgeCtx) = 0;
virtual StatusOr<SubPlan> transformEdge(EdgeContext* /* edgeCtx */) {
yixinglu marked this conversation as resolved.
Show resolved Hide resolved
return Status::Error("Unimplemented");
}

virtual const char* name() const = 0;

protected:
StartVidFinder() = default;
};

} // namespace graph
} // namespace nebula
#endif // GRAPH_PLANNER_MATCH_STARTVIDFINDER_H_
127 changes: 127 additions & 0 deletions src/graph/planner/match/VariableVertexIdSeek.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/* Copyright (c) 2023 vesoft inc. All rights reserved.
*
* This source code is licensed under Apache 2.0 License.
*/

#include "graph/planner/match/VariableVertexIdSeek.h"

#include "graph/planner/plan/Logic.h"
#include "graph/planner/plan/Query.h"
#include "graph/util/ExpressionUtils.h"

namespace nebula {
namespace graph {

bool VariableVertexIdSeek::matchNode(NodeContext *nodeCtx) {
auto whereClause = nodeCtx->bindWhereClause;
if (!whereClause || !whereClause->filter) {
return false;
}

auto nodeInfo = nodeCtx->info;
if (nodeInfo->alias.empty() || nodeInfo->anonymous) {
// require one named node
return false;
}

Expression *vidPredicate = whereClause->filter;
std::string refVarName;
if (!extractVidPredicate(nodeInfo->alias, vidPredicate, &refVarName)) {
return false;
}

// exclude the case where `refVarName` is from the path pattern of current match
if (!nodeCtx->nodeAliasesAvailable->count(refVarName)) {
return false;
}

nodeCtx->refVarName = refVarName;
return true;
}

StatusOr<SubPlan> VariableVertexIdSeek::transformNode(NodeContext *nodeCtx) {
auto *qctx = nodeCtx->qctx;
const auto &refVarName = nodeCtx->refVarName;
DCHECK(!refVarName.empty());

SubPlan plan;
auto argument = Argument::make(qctx, refVarName);
argument->setColNames({refVarName});
argument->setInputVertexRequired(false);
plan.root = plan.tail = argument;

nodeCtx->initialExpr = InputPropertyExpression::make(qctx->objPool(), refVarName);
return plan;
}

bool VariableVertexIdSeek::extractVidPredicate(const std::string &nodeAlias,
Expression *filter,
std::string *var) {
switch (filter->kind()) {
case Expression::Kind::kRelEQ:
case Expression::Kind::kRelIn: {
return isVidPredicate(nodeAlias, filter, var);
}
case Expression::Kind::kLogicalAnd: {
filter = filter->clone();
ExpressionUtils::pullAnds(filter);
auto logicAndExpr = static_cast<LogicalExpression *>(filter);
for (auto operand : logicAndExpr->operands()) {
if (isVidPredicate(nodeAlias, operand, var)) {
return true;
}
}
return false;
}
default: {
return false;
}
}
}

bool VariableVertexIdSeek::isVidPredicate(const std::string &nodeAlias,
const Expression *filter,
std::string *var) {
if (filter->kind() != Expression::Kind::kRelEQ && filter->kind() != Expression::Kind::kRelIn) {
return false;
}

auto relExpr = static_cast<const RelationalExpression *>(filter);
auto checkFunCall = [var, &nodeAlias](const Expression *expr, const Expression *varExpr) {
if (isIdFunCallExpr(nodeAlias, expr) && varExpr->kind() == Expression::Kind::kLabel) {
*var = static_cast<const LabelExpression *>(varExpr)->name();
return true;
}
return false;
};

if (relExpr->left()->kind() == Expression::Kind::kFunctionCall) {
return checkFunCall(relExpr->left(), relExpr->right());
}

if (relExpr->right()->kind() == Expression::Kind::kFunctionCall) {
return checkFunCall(relExpr->right(), relExpr->left());
}

return false;
}

bool VariableVertexIdSeek::isIdFunCallExpr(const std::string &nodeAlias, const Expression *filter) {
if (filter->kind() == Expression::Kind::kFunctionCall) {
auto funCallExpr = static_cast<const FunctionCallExpression *>(filter);
if (funCallExpr->name() == "id") {
DCHECK_EQ(funCallExpr->args()->numArgs(), 1u);
auto arg = funCallExpr->args()->args()[0];
if (arg->kind() == Expression::Kind::kLabel) {
auto labelExpr = static_cast<const LabelExpression *>(arg);
if (labelExpr->name() == nodeAlias) {
return true;
}
}
}
}
return false;
}

} // namespace graph
} // namespace nebula
Loading