diff --git a/src/main/java/apoc/path/PathExplorer.java b/src/main/java/apoc/path/PathExplorer.java index b29312b553..2c6def4ee9 100644 --- a/src/main/java/apoc/path/PathExplorer.java +++ b/src/main/java/apoc/path/PathExplorer.java @@ -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; @@ -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; } } diff --git a/src/test/java/apoc/path/ExpandPathTest.java b/src/test/java/apoc/path/ExpandPathTest.java index 7eea578bc7..0208412620 100644 --- a/src/test/java/apoc/path/ExpandPathTest.java +++ b/src/test/java/apoc/path/ExpandPathTest.java @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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> 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"))); + } }