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

Pattern expression supports reference local edge variable #5424

Merged
merged 1 commit into from
Mar 21, 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
34 changes: 34 additions & 0 deletions src/common/function/FunctionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ std::unordered_map<std::string, std::vector<TypeSignature>> FunctionManager::typ
TypeSignature({Value::Type::MAP}, Value::Type::DURATION)}},
{"extract", {TypeSignature({Value::Type::STRING, Value::Type::STRING}, Value::Type::LIST)}},
{"_nodeid", {TypeSignature({Value::Type::PATH, Value::Type::INT}, Value::Type::INT)}},
{"_edge", {TypeSignature({Value::Type::PATH, Value::Type::INT}, Value::Type::EDGE)}},
{"json_extract",
{TypeSignature({Value::Type::STRING}, Value::Type::MAP),
TypeSignature({Value::Type::STRING}, Value::Type::NULLVALUE)}},
Expand Down Expand Up @@ -2850,6 +2851,39 @@ FunctionManager::FunctionManager() {
}
};
}
{
auto &attr = functions_["_edge"];
attr.minArity_ = 2;
attr.maxArity_ = 2;
attr.isAlwaysPure_ = true;
attr.body_ = [](const auto &args) -> Value {
if (!args[0].get().isPath() || !args[1].get().isInt()) {
return Value::kNullBadType;
}

const auto &p = args[0].get().getPath();
const std::size_t edgeIndex = args[1].get().getInt();
if (edgeIndex < 0 || edgeIndex >= p.steps.size()) {
DLOG(FATAL) << "Out of range edge index.";
return Value::kNullOutOfRange;
}
if (edgeIndex == 0) {
Edge edge;
edge.src = p.src.vid;
edge.dst = p.steps[0].dst.vid;
edge.type = p.steps[0].type;
edge.ranking = p.steps[0].ranking;
return edge;
} else {
Edge edge;
edge.src = p.steps[edgeIndex - 1].dst.vid;
edge.dst = p.steps[edgeIndex].dst.vid;
edge.type = p.steps[edgeIndex].type;
edge.ranking = p.steps[edgeIndex].ranking;
return edge;
}
};
}
{
auto &attr = functions_["json_extract"];
// note, we don't support second argument(path) like MySQL JSON_EXTRACT for now
Expand Down
5 changes: 5 additions & 0 deletions src/graph/util/ParserUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ void ParserUtil::rewriteLC(QueryContext *qctx,
node->setAlias(newVarName);
}
}
for (auto &edge : matchPath.edges()) {
if (edge->alias() == oldVarName) {
edge->setAlias(newVarName);
}
}
return static_cast<Expression *>(mpp);
} break;
default:
Expand Down
6 changes: 5 additions & 1 deletion src/graph/validator/MatchValidator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1254,7 +1254,7 @@ Status MatchValidator::validatePathInWhere(
}
}
for (const auto &node : matchPath->nodes()) {
if (node->variableDefinedSource() == MatchNode::VariableDefinedSource::kExpression) {
if (node->variableDefinedSource() == VariableDefinedSource::kExpression) {
// Checked in visitor
continue;
}
Expand All @@ -1273,6 +1273,10 @@ Status MatchValidator::validatePathInWhere(
}
}
for (const auto &edge : matchPath->edges()) {
if (edge->variableDefinedSource() == VariableDefinedSource::kExpression) {
// Checked in visitor
continue;
}
if (!edge->alias().empty()) {
const auto find = availableAliases.find(edge->alias());
if (find == availableAliases.end()) {
Expand Down
34 changes: 29 additions & 5 deletions src/graph/visitor/ValidatePatternExpressionVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ void ValidatePatternExpressionVisitor::visit(MatchPathPatternExpression *expr) {
DCHECK(ok()) << expr->toString();
// don't need to process sub-expression
const auto &matchPath = expr->matchPath();
std::vector<Expression *> nodeFilters;
std::vector<Expression *> nodeOrEdgeFilters;
auto *pathList = InputPropertyExpression::make(pool_, matchPath.toString());
auto listElementVar = vctx_->anonVarGen()->getVar();
for (std::size_t i = 0; i < matchPath.nodes().size(); ++i) {
Expand All @@ -33,7 +33,7 @@ void ValidatePatternExpressionVisitor::visit(MatchPathPatternExpression *expr) {
if (find != localVariables_.end()) {
// TODO we should check variable is Node type
// from local variable
node->setVariableDefinedSource(MatchNode::VariableDefinedSource::kExpression);
node->setVariableDefinedSource(VariableDefinedSource::kExpression);
auto *listElement = VariableExpression::makeInner(pool_, listElementVar);
// Note: this require build path by node pattern order
auto *listElementId = FunctionCallExpression::make(
Expand All @@ -46,13 +46,37 @@ void ValidatePatternExpressionVisitor::visit(MatchPathPatternExpression *expr) {
auto *nodeValue = VariableExpression::makeInner(pool_, node->alias());
auto *nodeId = FunctionCallExpression::make(pool_, "id", {nodeValue});
auto *equal = RelationalExpression::makeEQ(pool_, listElementId, nodeId);
nodeFilters.emplace_back(equal);
nodeOrEdgeFilters.emplace_back(equal);
}
}
}
if (!nodeFilters.empty()) {
for (std::size_t i = 0; i < matchPath.edges().size(); ++i) {
const auto &edge = matchPath.edges()[i];
if (!edge->alias().empty()) {
const auto find = std::find(localVariables_.begin(), localVariables_.end(), edge->alias());
if (find != localVariables_.end()) {
// TODO we should check variable is Edge type
// from local variable
edge->setVariableDefinedSource(VariableDefinedSource::kExpression);
auto *listElement = VariableExpression::makeInner(pool_, listElementVar);
// Note: this require build path by node pattern order
auto *edgeInPath = FunctionCallExpression::make(
pool_,
"_edge",
{listElement, ConstantExpression::make(pool_, static_cast<int64_t>(i))});
// The alias of node is converted to a inner variable.
// e.g. MATCH (v:player) WHERE [t in [v] | (v)-[:like]->(t)] RETURN v
// More cases could be found in PathExprRefLocalVariable.feature
auto *edgeValue = VariableExpression::makeInner(pool_, edge->alias());
auto *equal = RelationalExpression::makeEQ(pool_, edgeInPath, edgeValue);
nodeOrEdgeFilters.emplace_back(equal);
}
}
}

if (!nodeOrEdgeFilters.empty()) {
auto genList = ListComprehensionExpression::make(
pool_, listElementVar, pathList, andAll(nodeFilters), nullptr);
pool_, listElementVar, pathList, andAll(nodeOrEdgeFilters), nullptr);
expr->setGenList(genList);
}
}
Expand Down
32 changes: 23 additions & 9 deletions src/parser/MatchPath.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ class MatchEdgeProp final {
std::unique_ptr<MatchStepRange> range_;
};

enum class VariableDefinedSource {
kUnknown,
kExpression, // from upper expression
kMatchClause, // from previous match clause
};

class MatchEdge final {
public:
using Direction = nebula::storage::cpp2::EdgeDirection;
Expand All @@ -99,6 +105,10 @@ class MatchEdge final {
return direction_;
}

void setAlias(const std::string& alias) {
alias_ = alias;
}

const std::string& alias() const {
return alias_;
}
Expand All @@ -115,6 +125,14 @@ class MatchEdge final {
return range_.get();
}

VariableDefinedSource variableDefinedSource() const {
return variableDefinedSource_;
}

void setVariableDefinedSource(VariableDefinedSource source) {
variableDefinedSource_ = source;
}

std::string toString() const;

MatchEdge clone() const {
Expand All @@ -139,6 +157,8 @@ class MatchEdge final {
std::vector<std::unique_ptr<std::string>> types_;
std::unique_ptr<MatchStepRange> range_;
MapExpression* props_{nullptr};
// Only used for pattern expression
VariableDefinedSource variableDefinedSource_{VariableDefinedSource::kUnknown};
};

class MatchNodeLabel final {
Expand Down Expand Up @@ -218,9 +238,9 @@ class MatchNodeLabelList final {

class MatchNode final {
public:
MatchNode(const std::string& alias = "",
MatchNodeLabelList* labels = nullptr,
Expression* props = nullptr) {
explicit MatchNode(const std::string& alias = "",
MatchNodeLabelList* labels = nullptr,
Expression* props = nullptr) {
alias_ = alias;
labels_.reset(labels);
props_ = static_cast<MapExpression*>(props);
Expand Down Expand Up @@ -260,12 +280,6 @@ class MatchNode final {
return me;
}

enum class VariableDefinedSource {
kUnknown,
kExpression, // from upper expression
kMatchClause, // from previous match clause
};

VariableDefinedSource variableDefinedSource() const {
return variableDefinedSource_;
}
Expand Down
1 change: 0 additions & 1 deletion src/storage/query/GetNeighborsProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ extern ProcessorCounters kGetNeighborsCounters;
*/
class GetNeighborsProcessor
: public QueryBaseProcessor<cpp2::GetNeighborsRequest, cpp2::GetNeighborsResponse> {

public:
/**
* @brief Construct instance of GetNeighborsProcessor
Expand Down
13 changes: 11 additions & 2 deletions tests/tck/features/match/Base.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1291,8 +1291,17 @@ Feature: Basic match
WHERE size([ii in e WHERE (v)-[ii]-(v2) | ii])>1
RETURN count(*) AS cnt
"""
# FIXME(czp): Fix this case after https://github.com/vesoft-inc/nebula/issues/5289 closed
Then a SemanticError should be raised at runtime: PatternExpression are not allowed to introduce new variables: `ii'.
Then the result should be, in any order, with relax comparison:
| cnt |
| 0 |
When executing query:
"""
MATCH p=(v:player)-[]->() where [ii in relationships(p) where (v)-[ii]->()]
RETURN count(*) AS cnt
"""
Then the result should be, in any order, with relax comparison:
| cnt |
| 243 |

# Then the result should be, in any order:
# | cnt |
Expand Down
58 changes: 58 additions & 0 deletions tests/tck/features/match/PathExprRefLocalVariable.feature
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,27 @@ Feature: Path expression reference local defined variables
| "Vince Carter" |
| "Rajon Rondo" |
| "Ray Allen" |
When executing query:
"""
MATCH p=(v:player)-[]-() where [ii in nodes(p) where (v)-[:like]->(ii)] RETURN count(*) AS count
"""
Then the result should be, in any order:
| count |
| 133 |
When executing query:
"""
MATCH p=(v:player)-[]->() where [ii in relationships(p) where (v)-[ii]->(:team)] return count(*) AS count
"""
Then the result should be, in any order:
| count |
| 152 |
When executing query:
"""
MATCH p=(v:player{name:"Tim Duncan"})-[]->() where [ii in relationships(p) where (v)-[ii]->(:team)] return count(*) AS count
"""
Then the result should be, in any order:
| count |
| 1 |

Scenario: In With
When executing query:
Expand Down Expand Up @@ -194,6 +215,26 @@ Feature: Path expression reference local defined variables
| p |
| [[<("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})>]] |
| [[<("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})>]] |
When executing query:
"""
MATCH (v:player)-[e:like{likeness:80}]->() WITH [ii in [e] | (v)-[ii]->()] AS p return p
"""
Then the result should be, in any order, with relax comparison:
| p |
| [[<("Luka Doncic" :player{age: 20, name: "Luka Doncic"})-[:like@0 {likeness: 80}]->("James Harden" :player{age: 29, name: "James Harden"})>]] |
| [[<("Joel Embiid" :player{age: 25, name: "Joel Embiid"})-[:like@0 {likeness: 80}]->("Ben Simmons" :player{age: 22, name: "Ben Simmons"})>]] |
| [[<("Jason Kidd" :player{age: 45, name: "Jason Kidd"})-[:like@0 {likeness: 80}]->("Vince Carter" :player{age: 42, name: "Vince Carter"})>]] |
| [[<("James Harden" :player{age: 29, name: "James Harden"})-[:like@0 {likeness: 80}]->("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})>]] |
| [[<("Dirk Nowitzki" :player{age: 40, name: "Dirk Nowitzki"})-[:like@0 {likeness: 80}]->("Jason Kidd" :player{age: 45, name: "Jason Kidd"})>]] |
| [[<("Dirk Nowitzki" :player{age: 40, name: "Dirk Nowitzki"})-[:like@0 {likeness: 80}]->("Steve Nash" :player{age: 45, name: "Steve Nash"})>]] |
| [[<("Danny Green" :player{age: 31, name: "Danny Green"})-[:like@0 {likeness: 80}]->("LeBron James" :player{age: 34, name: "LeBron James"})>]] |
| [[<("Damian Lillard" :player{age: 28, name: "Damian Lillard"})-[:like@0 {likeness: 80}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})>]] |
| [[<("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:like@0 {likeness: 80}]->("Tim Duncan" :player{age: 42, name: "Tim Duncan"} :bachelor{name: "Tim Duncan", speciality: "psychology"})>]] |
| [[<("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :player{age: 42, name: "Tim Duncan"} :bachelor{name: "Tim Duncan", speciality: "psychology"})>]] |
| [[<("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:like@0 {likeness: 80}]->("Tim Duncan" :player{age: 42, name: "Tim Duncan"} :bachelor{name: "Tim Duncan", speciality: "psychology"})>]] |
| [[<("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:like@0 {likeness: 80}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})>]] |
| [[<("Ben Simmons" :player{age: 22, name: "Ben Simmons"})-[:like@0 {likeness: 80}]->("Joel Embiid" :player{age: 25, name: "Joel Embiid"})>]] |
| [[<("Aron Baynes" :player{age: 32, name: "Aron Baynes"})-[:like@0 {likeness: 80}]->("Tim Duncan" :player{age: 42, name: "Tim Duncan"} :bachelor{name: "Tim Duncan", speciality: "psychology"})>]] |

Scenario: In Return
When executing query:
Expand Down Expand Up @@ -224,6 +265,14 @@ Feature: Path expression reference local defined variables
| [<("Luka Doncic" :player{age: 20, name: "Luka Doncic"})-[:like@0 {likeness: 80}]->("James Harden" :player{age: 29, name: "James Harden"})>] | [] | "Luka Doncic" |
| [<("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :player{age: 42, name: "Tim Duncan"} :bachelor{name: "Tim Duncan", speciality: "psychology"})>] | [] | "Shaquille O'Neal" |
| [<("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:like@0 {likeness: 80}]->("Tim Duncan" :player{age: 42, name: "Tim Duncan"} :bachelor{name: "Tim Duncan", speciality: "psychology"})>] | [] | "Tiago Splitter" |
When executing query:
"""
MATCH p1 = (v:player{name: 'Tim Duncan'})-[:like]->() return [t in relationships(p1) | (v)-[t]->()] AS p2
"""
Then the result should be, in any order:
| p2 |
| [[<("Tim Duncan" :player{age: 42, name: "Tim Duncan"} :bachelor{name: "Tim Duncan", speciality: "psychology"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})>]] |
| [[<("Tim Duncan" :player{age: 42, name: "Tim Duncan"} :bachelor{name: "Tim Duncan", speciality: "psychology"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})>]] |

Scenario: In Unwind
When executing query:
Expand All @@ -234,6 +283,15 @@ Feature: Path expression reference local defined variables
| p |
| [<("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})>] |
| [<("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})>] |
When executing query:
"""
MATCH p1 = (v:player{name: 'Tony Parker'})-[:like]->(), (t:team {name: "Spurs"}) UNWIND [t in relationships(p1) | (v)-[t]->()] AS p2 RETURN p2
"""
Then the result should be, in any order:
| p2 |
| [<("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 90}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})>] |
| [<("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})>] |
| [<("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Tim Duncan" :player{age: 42, name: "Tim Duncan"} :bachelor{name: "Tim Duncan", speciality: "psychology"})>] |
When executing query:
"""
MATCH (v:player{name: 'Tim Duncan'})-[e:like*1..3]->(n), (t:team {name: "Spurs"}) WITH v, e, collect(distinct n) AS ns UNWIND [n in ns | ()-[e*2..4]->(n:player)] AS p RETURN count(p) AS count
Expand Down