Skip to content

Commit

Permalink
Support variable when seeking by id in match clause (vesoft-inc#2672)
Browse files Browse the repository at this point in the history
* Support variable when seek by id in match clause

* Fix tck

Co-authored-by: Yee <2520865+yixinglu@users.noreply.github.com>
  • Loading branch information
nebula-bots and yixinglu authored Apr 20, 2023
1 parent 478ae6a commit f0dc078
Show file tree
Hide file tree
Showing 16 changed files with 1,084 additions and 60 deletions.
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 */) {
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

0 comments on commit f0dc078

Please sign in to comment.