Skip to content

Commit

Permalink
Add end node operator to path expander label filter (#327)
Browse files Browse the repository at this point in the history
* Add end node operator to path expander label filter

* Updated end node operator in the code and tests
  • Loading branch information
InverseFalcon authored and sarmbruster committed Mar 19, 2017
1 parent 3d80848 commit 7795891
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 16 deletions.
23 changes: 12 additions & 11 deletions docs/expand.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Expand from start node following the given relationships from min to max-level a
----
CALL apoc.path.expand(startNode <id>|Node, relationshipFilter, labelFilter, minLevel, maxLevel )
CALL apoc.path.expand(startNode <id>|Node|list, 'TYPE|TYPE_OUT>|<TYPE_IN', '+YesLabel|-NoLabel', minLevel, maxLevel ) yield path
CALL apoc.path.expand(startNode <id>|Node|list, 'TYPE|TYPE_OUT>|<TYPE_IN', '+YesLabel|-NoLabel|/TerminationLabel|>EndNodeLabel', minLevel, maxLevel ) yield path
----

==== Relationship Filter
Expand All @@ -35,9 +35,9 @@ Syntax: `[<]RELATIONSHIP_TYPE1[>]|[<]RELATIONSHIP_TYPE2[>]|...`

==== Label Filter

Syntax: `[+-/]LABEL1|LABEL2|...`
Syntax: `[+-/>]LABEL1|LABEL2|...`

Only one type of operation allowed in the label filter (`+` or `-` or `/`, never more than one).
Only one type of operation allowed in the label filter (`+` or `-` or `/` or `>`, never more than one).

With APOC 3.2.x.x, label filters will no longer apply to starting nodes of the expansion by default.

Expand All @@ -47,6 +47,7 @@ With APOC 3.2.x.x, label filters will no longer apply to starting nodes of the e
| +Friend | Friend | whitelist filter - include label, all nodes in the path must have a label in the whitelist
| -Foe | Foe | blacklist filter - exclude label, no node in the path will have a label in the blacklist
| /Friend | Friend | termination filter - only return paths to :Friend nodes, and stop further traversal along a path after reaching a :Friend
| >Friend | Friend | end node filter - only return paths to :Friend nodes, but continue traversal along a path after reaching a :Friend
|===


Expand All @@ -71,7 +72,7 @@ call apoc.path.expand(p,"ACTED_IN>|PRODUCED<|FOLLOWS<","+Movie|Person",1,2) yiel
return p, pp
----

.Termination label filter example
.Termination and end node label filter example

We will first set a `:Western` label on some nodes.

Expand All @@ -82,7 +83,7 @@ where p.name in ['Clint Eastwood', 'Gene Hackman']
set p:Western
----

Now expand from 'Keanu Reeves' to all `:Western` nodes.
Now expand from 'Keanu Reeves' to all `:Western` nodes with a termination filter:

[source,cypher]
----
Expand All @@ -92,7 +93,9 @@ return path
----

The one returned path only matches up to 'Gene Hackman'.
While there is a path from 'Keanu Reeves' to 'Clint Eastwood' through 'Gene Hackman', no further expansion is permitted through a node in the termination filter list.
While there is a path from 'Keanu Reeves' to 'Clint Eastwood' through 'Gene Hackman', no further expansion is permitted through a node in the termination filter.

If you didn't want to stop expansion on reaching 'Gene Hackman', and wanted 'Clint Eastwood' returned as well, use the end node filter instead (`>`).


== Expand with Config
Expand All @@ -108,7 +111,7 @@ Takes an additional map parameter, `config`, to provide configuration options:
{minLevel: -1|number,
maxLevel: -1|number,
relationshipFilter: '[<]RELATIONSHIP_TYPE1[>]|[<]RELATIONSHIP_TYPE2[>]|...',
labelFilter: '[+-]LABEL1|LABEL2|...',
labelFilter: '[+-/>]LABEL1|LABEL2|...',
uniqueness: RELATIONSHIP_PATH|NONE|NODE_GLOBAL|NODE_LEVEL|NODE_PATH|NODE_RECENT|
RELATIONSHIP_GLOBAL|RELATIONSHIP_LEVEL|RELATIONSHIP_RECENT,
bfs: true|false,
Expand All @@ -132,11 +135,9 @@ Use `filterStartNode = false` when you want your label filter to only apply to a

.Limit

Only when using the termination label filter `/`, you can use the `limit` config parameter to limit the number of paths returned.

When using `bfs:true` (which is the default for all expand procedures), this has the effect of returning paths to the `n` nearest nodes with labels in the termination filter, where `n` is the limit given.
Only when using the termination label filter `/` or end node filter `>`, you can use the `limit` config parameter to limit the number of paths returned.

Remember that when using the termination filter, further expansion is stopped when a termination node is reached, so other termination nodes beyond them may not be reachable or returned if there is no alternate path to them.
When using `bfs:true` (which is the default for all expand procedures), this has the effect of returning paths to the `n` nearest nodes with labels in the termination or end node filter, where `n` is the limit given.

The default limit value, `-1`, means no limit.

Expand Down
7 changes: 4 additions & 3 deletions docs/overview.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ examples

(thanks @keesvegter)

The apoc.path.expand procedure makes it possible to do variable length path traversals where you can specify the direction of the relationship per relationship type and a list of Label names which act as a "whitelist" or a "blacklist". The procedure will return a list of Paths in a variable name called "path".
The apoc.path.expand procedure makes it possible to do variable length path traversals where you can specify the direction of the relationship per relationship type and a list of Label names which act as a "whitelist" or a "blacklist" or define end nodes for the expansion. The procedure will return a list of Paths in a variable name called "path".

[cols="1m,5"]
|===
Expand Down Expand Up @@ -897,9 +897,9 @@ Syntax: `[<]RELATIONSHIP_TYPE1[>]|[<]RELATIONSHIP_TYPE2[>]|...`

=== Label Filter

Syntax: `[+-/]LABEL1|LABEL2|...`
Syntax: `[+-/>]LABEL1|LABEL2|...`

Only one type of operation allowed in the label filter (`+` or `-` or `/`, never more than one).
Only one type of operation allowed in the label filter (`+` or `-` or `/` or `>`, never more than one).

With APOC 3.2.x.x, label filters will no longer apply to starting nodes of the expansion by default.

Expand All @@ -909,6 +909,7 @@ With APOC 3.2.x.x, label filters will no longer apply to starting nodes of the e
| +Friend | Friend | whitelist filter - include label, all nodes in the path must have a label in the whitelist
| -Foe | Foe | blacklist filter - exclude label, no node in the path will have a label in the blacklist
| /Friend | Friend | termination filter - only return paths to :Friend nodes, and stop further traversal along a path after reaching a :Friend
| >Friend | Friend | end node filter - only return paths to :Friend nodes, but continue traversal along a path after reaching a :Friend
|===

=== Uniqueness
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/apoc/path/PathExplorer.java
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ public LabelEvaluator(String labelFilter, boolean filterStartNode, long limit) {
@Override
public Evaluation evaluate(Path path) {
if (path.length() == 0 && !filterStartNode) {
if (operator == '/') {
if (operator == '/' || operator == '>') {
return EXCLUDE_AND_CONTINUE;
} else {
return INCLUDE_AND_CONTINUE;
Expand All @@ -217,11 +217,12 @@ public Evaluation evaluate(Path path) {
case '-':
result = labelExists(check) ? EXCLUDE_AND_PRUNE : INCLUDE_AND_CONTINUE;
break;
case '>':
case '/':
if (limit != -1 && resultCount >= limit) {
result = EXCLUDE_AND_PRUNE;
} else if (labelExists(check)) {
result = INCLUDE_AND_PRUNE;
result = operator == '/' ? INCLUDE_AND_PRUNE : INCLUDE_AND_CONTINUE;
resultCount++;
} else {
result = EXCLUDE_AND_CONTINUE;
Expand Down
38 changes: 38 additions & 0 deletions src/test/java/apoc/path/ExpandPathTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package apoc.path;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import apoc.util.Util;
import org.junit.*;
Expand All @@ -12,6 +13,7 @@

import apoc.util.TestUtil;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -102,4 +104,40 @@ public void testExplorePathWithLimitReturnsLimitedResults() {
assertEquals("Clint Eastwood", path.endNode().getProperty("name"));
});
}

@Test
public void testExplorePathWithEndNodeLabel() {
db.execute("MATCH (c:Person) WHERE c.name in ['Clint Eastwood', 'Gene Hackman'] SET c:Western");

TestUtil.testResult(db,
"MATCH (k:Person {name:'Keanu Reeves'}) " +
"CALL apoc.path.expandConfig(k, {relationshipFilter:'ACTED_IN|PRODUCED|DIRECTED', labelFilter:'>Western', uniqueness: 'NODE_GLOBAL'}) yield path " +
"return path",
result -> {
List<Map<String, Object>> maps = Iterators.asList(result);
assertEquals(2, maps.size());
Path path = (Path) maps.get(0).get("path");
assertEquals("Gene Hackman", path.endNode().getProperty("name"));;
path = (Path) maps.get(1).get("path");
assertEquals("Clint Eastwood", path.endNode().getProperty("name"));
});
}

@Test
public void testExplorePathWithEndNodeLabelAndLimit() {
db.execute("MATCH (c:Person) WHERE c.name in ['Clint Eastwood', 'Gene Hackman', 'Christian Bale'] SET c:Western");

TestUtil.testResult(db,
"MATCH (k:Person {name:'Keanu Reeves'}) " +
"CALL apoc.path.expandConfig(k, {relationshipFilter:'ACTED_IN|PRODUCED|DIRECTED', labelFilter:'>Western', uniqueness: 'NODE_GLOBAL', limit:2}) yield path " +
"return path",
result -> {
List<Map<String, Object>> maps = Iterators.asList(result);
assertEquals(2, maps.size());
Path path = (Path) maps.get(0).get("path");
assertEquals("Gene Hackman", path.endNode().getProperty("name"));;
path = (Path) maps.get(1).get("path");
assertEquals("Clint Eastwood", path.endNode().getProperty("name"));
});
}
}

0 comments on commit 7795891

Please sign in to comment.