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

Prevent path expander termination filter from filtering below minLevel #379

Merged
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
21 changes: 13 additions & 8 deletions src/main/java/apoc/path/PathExplorer.java
Original file line number Diff line number Diff line change
Expand Up @@ -234,20 +234,27 @@ public LabelEvaluator(String labelFilter, boolean filterStartNode, long limit, i
@Override
public Evaluation evaluate(Path path) {
int depth = path.length();
// if start node shouldn't be filtered
Node check = path.endNode();

// if start node shouldn't be filtered, exclude/include based on if using termination/endnode filter or not
// minLevel evaluator will separately enforce exclusion if we're below minLevel
if (depth == 0 && !filterStartNode) {
return whitelistAllowedEvaluation;
}

// below minLevel always exclude; continue if blacklist and whitelist allow it
if (depth < minLevel) {
return labelExists(check, blacklistLabels) || !whitelistAllowed(check) ? EXCLUDE_AND_PRUNE : EXCLUDE_AND_CONTINUE;
}

// cut off expansion when we reach the limit
if (limit != -1 && resultCount >= limit) {
return EXCLUDE_AND_PRUNE;
}

Node check = path.endNode();
Evaluation result = labelExists(check, blacklistLabels) ? EXCLUDE_AND_PRUNE :
labelExists(check, terminationLabels) ? filterEndNode(check, depth, true) :
labelExists(check, endNodeLabels) ? filterEndNode(check, depth, false) :
labelExists(check, terminationLabels) ? filterEndNode(check, true) :
labelExists(check, endNodeLabels) ? filterEndNode(check, false) :
whitelistAllowed(check) ? whitelistAllowedEvaluation : EXCLUDE_AND_PRUNE;

return result;
Expand All @@ -270,10 +277,8 @@ private boolean whitelistAllowed(Node node) {
return whitelistLabels.isEmpty() || labelExists(node, whitelistLabels);
}

private Evaluation filterEndNode(Node node, int depth, boolean isTerminationFilter) {
if (depth >= minLevel) {
resultCount++;
}
private Evaluation filterEndNode(Node node, boolean isTerminationFilter) {
resultCount++;
return isTerminationFilter || !whitelistAllowed(node) ? INCLUDE_AND_PRUNE : INCLUDE_AND_CONTINUE;
}
}
Expand Down
32 changes: 27 additions & 5 deletions src/test/java/apoc/path/ExpandPathTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public void testBlacklistBeforeWhitelist() {
}

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

TestUtil.testResult(db,
Expand All @@ -171,7 +171,7 @@ public void testBlacklistBeforeTerminationList() {
}

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

TestUtil.testResult(db,
Expand All @@ -185,7 +185,7 @@ public void testBlacklistBeforeEndNodeList() {
}

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

TestUtil.testResult(db,
Expand All @@ -201,7 +201,7 @@ public void testTerminationListBeforeWhitelist() {
}

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

TestUtil.testResult(db,
Expand All @@ -217,7 +217,7 @@ public void testTerminationListBeforeEndNodeList() {
}

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

TestUtil.testResult(db,
Expand Down Expand Up @@ -247,4 +247,26 @@ public void testLimitPlaysNiceWithMinLevel() {
assertEquals("Clint Eastwood", path.endNode().getProperty("name"));
});
}

@Test
public void testTerminationFilterDoesNotPruneBelowMinLevel() {
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', minLevel:3}) yield path " +
"return path",
result -> {
List<Map<String, Object>> maps = Iterators.asList(result);
assertEquals(1, maps.size());
Path path = (Path) maps.get(0).get("path");
assertEquals("Clint Eastwood", path.endNode().getProperty("name"));
});
}

@Test
public void testFilterStartNodeFalseDoesNotFilterStartNodeWhenBelowMinLevel() throws Throwable {
String query = "MATCH (m:Movie {title: 'The Matrix'}) CALL apoc.path.expandConfig(m,{labelFilter:'+Person', minLevel:1, maxLevel:2, filterStartNode:false}) yield path return count(*) as c";
TestUtil.testCall(db, query, (row) -> assertEquals(8L,row.get("c")));
}
}