Skip to content

Commit

Permalink
Merge pull request #1815 from kuzudb/self-loop-edge
Browse files Browse the repository at this point in the history
Add self-loop-edge
  • Loading branch information
andyfengHKU committed Jul 14, 2023
2 parents 9f7007a + 21c8c7d commit 0662374
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 12 deletions.
3 changes: 0 additions & 3 deletions src/binder/bind/bind_graph_pattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,6 @@ std::shared_ptr<RelExpression> Binder::bindQueryRel(const RelPattern& relPattern
default:
throw common::NotImplementedException("Binder::bindQueryRel");
}
if (srcNode->getUniqueName() == dstNode->getUniqueName()) {
throw BinderException("Self-loop rel " + parsedName + " is not supported.");
}
// bind variable length
std::shared_ptr<RelExpression> queryRel;
if (QueryRelTypeUtils::isRecursive(relPattern.getRelType())) {
Expand Down
25 changes: 25 additions & 0 deletions src/binder/bind/bind_reading_clause.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,37 @@ std::unique_ptr<BoundReadingClause> Binder::bindMatchClause(const ReadingClause&
if (matchClause.hasWhereClause()) {
whereExpression = bindWhereExpression(*matchClause.getWhereClause());
}
// Rewrite self loop edge
// e.g. rewrite (a)-[e]->(a) as [a]-[e]->(b) WHERE id(a) = id(b)
expression_vector selfLoopEdgePredicates;
auto graphCollection = boundMatchClause->getQueryGraphCollection();
for (auto i = 0; i < graphCollection->getNumQueryGraphs(); ++i) {
auto queryGraph = graphCollection->getQueryGraph(i);
for (auto& queryRel : queryGraph->getQueryRels()) {
if (!queryRel->isSelfLoop()) {
continue;
}
auto src = queryRel->getSrcNode();
auto dst = queryRel->getDstNode();
auto newDst = createQueryNode(dst->getVariableName(), dst->getTableIDs());
queryGraph->addQueryNode(newDst);
queryRel->setDstNode(newDst);
auto predicate = expressionBinder.createEqualityComparisonExpression(
src->getInternalIDProperty(), newDst->getInternalIDProperty());
selfLoopEdgePredicates.push_back(std::move(predicate));
}
}
for (auto& predicate : selfLoopEdgePredicates) {
whereExpression =
expressionBinder.combineConjunctiveExpressions(predicate, whereExpression);
}
// Rewrite key value pairs in MATCH clause as predicate
for (auto& [key, val] : propertyCollection->getKeyVals()) {
auto predicate = expressionBinder.createEqualityComparisonExpression(key, val);
whereExpression =
expressionBinder.combineConjunctiveExpressions(predicate, whereExpression);
}

boundMatchClause->setWhereExpression(std::move(whereExpression));
return boundMatchClause;
}
Expand Down
2 changes: 2 additions & 0 deletions src/include/binder/expression/expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class Expression : public std::enable_shared_from_this<Expression> {

expression_vector splitOnAND();

inline bool operator==(const Expression& rhs) const { return uniqueName == rhs.uniqueName; }

virtual std::string toString() const = 0;

virtual std::unique_ptr<Expression> copy() const {
Expand Down
2 changes: 2 additions & 0 deletions src/include/binder/expression/node_rel_expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class NodeOrRelExpression : public Expression {
variableName(std::move(variableName)), tableIDs{std::move(tableIDs)} {}
virtual ~NodeOrRelExpression() override = default;

inline std::string getVariableName() const { return variableName; }

inline void addTableIDs(const std::vector<common::table_id_t>& tableIDsToAdd) {
auto tableIDsSet = getTableIDsSet();
for (auto tableID : tableIDsToAdd) {
Expand Down
3 changes: 3 additions & 0 deletions src/include/binder/expression/rel_expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class RelExpression : public NodeOrRelExpression {

inline std::shared_ptr<NodeExpression> getSrcNode() const { return srcNode; }
inline std::string getSrcNodeName() const { return srcNode->getUniqueName(); }
inline void setDstNode(std::shared_ptr<NodeExpression> node) { dstNode = std::move(node); }
inline std::shared_ptr<NodeExpression> getDstNode() const { return dstNode; }
inline std::string getDstNodeName() const { return dstNode->getUniqueName(); }

Expand All @@ -72,6 +73,8 @@ class RelExpression : public NodeOrRelExpression {
return recursiveInfo->lengthExpression;
}

inline bool isSelfLoop() const { return *srcNode == *dstNode; }

private:
std::shared_ptr<NodeExpression> srcNode;
std::shared_ptr<NodeExpression> dstNode;
Expand Down
5 changes: 0 additions & 5 deletions test/test_files/exceptions/binder/binder_error.test
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,6 @@ Binder exception: PropertyName: _id is an internal reserved propertyName.
---- error
Binder exception: Node/Rel person1 does not exist.

-LOG SelfLoopRel
-STATEMENT MATCH (a:person)-[e:knows]->(a) RETURN COUNT(*)
---- error
Binder exception: Self-loop rel e is not supported.

-LOG InvalidLimitNumberType
-STATEMENT MATCH (a:person) RETURN a.age LIMIT "abc"
---- error
Expand Down
30 changes: 30 additions & 0 deletions test/test_files/tck/match/match2.test
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,33 @@ Binder exception: No rel table exists in database.
-STATEMENT MATCH (:A)-[r]->(:B) RETURN r;
---- 1
(0:0)-{_LABEL: T1, _ID: 2:0}->(1:0)

# Matching a self-loop with an undirected relationship pattern
-CASE Scenario3&4
-STATEMENT CREATE NODE TABLE A(name STRING, PRIMARY KEY(name));
---- ok
-STATEMENT CREATE REL TABLE T(FROM A TO A);
---- ok
-STATEMENT CREATE (a:A {name:'xx'}) CREATE (a)-[:T]->(a);
---- ok
# Note: tck test only returns 1 tuple. Though we shouldn't do so if the semantic of undirected is to match
# both fwd and bwd adjList.
-STATEMENT MATCH (a)-[:T]-(a) RETURN a;
---- 2
{_ID: 0:0, _LABEL: A, name: xx}
{_ID: 0:0, _LABEL: A, name: xx}
-STATEMENT MATCH (a)-[:T]->(a) RETURN a;
---- 1
{_ID: 0:0, _LABEL: A, name: xx}

# Match relationship with inline property value
-CASE Scenario5
-STATEMENT CREATE NODE TABLE A(ID INT64, PRIMARY KEY(ID));
---- ok
-STATEMENT CREATE REL TABLE KNOWS(FROM A TO A, name STRING);
---- ok
-STATEMENT CREATE (:A {ID: 1})<-[:KNOWS {name: 'monkey'}]-(:A {ID: 3})-[:KNOWS {name: 'woot'}]->(:A {ID: 5});
---- ok
-STATEMENT MATCH (n)-[r:KNOWS {name: 'monkey'}]->(a) RETURN a;
---- 1
{_ID: 0:0, _LABEL: A, ID: 1}
13 changes: 9 additions & 4 deletions test/test_files/tinysnb/var_length_extend/multi_label.test
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,31 @@
4|[3:1,5:0]|[,2015]
6|[3:2,5:1]|[,2010]

-LOG MixMultiLabelTest2
-LOG MultiLabelTest1
-STATEMENT MATCH (a:person)-[e:meets|:marries|:studyAt*2..2]->(b) WHERE a.fName = 'Alice' RETURN b.ID, properties(rels(e), '_id'), properties(nodes(e), 'fName')
---- 4
1|[6:0,4:1]|[Bob]
1|[7:0,4:1]|[Bob]
5|[6:0,6:1]|[Bob]
5|[7:0,6:1]|[Bob]

-LOG MixMultiLabelTest3
-LOG MultiLabelTest2
-STATEMENT MATCH (a:person)-[e:meets|:marries|:studyAt*2..2]->(b) WHERE a.fName = 'Alice' AND b.ID < 5 RETURN COUNT(*)
-ENUMERATE
---- 1
2

-LOG MixMultiLabelTest4
-LOG MultiLabelTest3
-STATEMENT MATCH (a:person)-[e*2..2]->(b:organisation) WHERE a.fName = 'Alice' RETURN COUNT(*)
---- 1
5

-LOG MixMultiLabelTest5
-LOG MultiLabelTest
-STATEMENT MATCH (a:person)-[e*2..2 (r, _ | WHERE offset(id(r)) > 0)]->(b:organisation) WHERE a.fName = 'Alice' RETURN rels(e)
---- 1
[(0:0)-{_LABEL: knows, _ID: 3:2, date: 2021-06-30, meetTime: 2012-12-11 20:07:22, validInterval: 10 days, comments: [ioji232,jifhe8w99u43434]}->(0:3),(0:3)-{_LABEL: workAt, _ID: 5:1, year: 2010, grading: [2.100000,4.400000], rating: 7.600000}->(1:2)]

-LOG MultiLabelSelfLoopTest
-STATEMENT MATCH (a)-[e:studyAt|:knows*2..3]-(a) WHERE a.ID = 1 RETURN COUNT(*)
---- 1
7
5 changes: 5 additions & 0 deletions test/test_files/tinysnb/var_length_extend/n_n.test
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,8 @@ Greg
-STATEMENT MATCH (a:person)-[e:knows*1..2 (r,_ | WHERE list_contains(r.comments, 'rnme'))]->(b:person) WHERE a.fName='Alice' RETURN COUNT(*)
---- 1
1

-LOG SelfLoop
-STATEMENT MATCH (a:person)-[e:knows*1..3]->(a) WHERE a.fName='Alice' RETURN COUNT(*)
---- 1
9

0 comments on commit 0662374

Please sign in to comment.