/** * Copyright (c) 2002-2010 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.traversal; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.junit.BeforeClass; import org.junit.Test; import org.neo4j.graphdb.Path; import org.neo4j.graphdb.traversal.TraversalDescription; import org.neo4j.graphdb.traversal.Traverser; import org.neo4j.helpers.Predicate; import org.neo4j.kernel.Traversal; import org.neo4j.kernel.Uniqueness; public class DepthPitfallGraphTest extends AbstractTestBase { /* Layout: * _(2)--__ * / \ * (1)-(3)-_ (6) * |\_ \ / * | (4)__ \/ * \_______(5) */ private static final String[] THE_WORLD_AS_WE_KNOW_IT = new String[] { "1 TO 2", "1 TO 3", "1 TO 4", "5 TO 3", "1 TO 5", "4 TO 5", "2 TO 6", "5 TO 6" }; private static final String[] NODE_UNIQUE_PATHS = new String[] { "1", "1,2", "1,2,6", "1,2,6,5", "1,2,6,5,3", "1,2,6,5,4", "1,3", "1,3,5", "1,3,5,4", "1,3,5,6", "1,3,5,6,2", "1,4", "1,4,5", "1,4,5,3", "1,4,5,6", "1,4,5,6,2", "1,5", "1,5,3", "1,5,4", "1,5,6", "1,5,6,2" }; private static final String[] RELATIONSHIP_UNIQUE_EXTRA_PATHS = new String[] { "1,2,6,5,1", "1,2,6,5,1,3", "1,2,6,5,1,3,5", "1,2,6,5,1,3,5,4", "1,2,6,5,1,3,5,4,1", "1,2,6,5,1,4", "1,2,6,5,1,4,5", "1,2,6,5,1,4,5,3", "1,2,6,5,1,4,5,3,1", "1,2,6,5,3,1", "1,2,6,5,3,1,4", "1,2,6,5,3,1,4,5", "1,2,6,5,3,1,4,5,1", "1,2,6,5,3,1,5", "1,2,6,5,3,1,5,4", "1,2,6,5,3,1,5,4,1", "1,2,6,5,4,1", "1,2,6,5,4,1,3", "1,2,6,5,4,1,3,5", "1,2,6,5,4,1,3,5,1", "1,2,6,5,4,1,5", "1,2,6,5,4,1,5,3", "1,2,6,5,4,1,5,3,1", "1,3,5,1", "1,3,5,1,2", "1,3,5,1,2,6", "1,3,5,1,2,6,5", "1,3,5,1,2,6,5,4", "1,3,5,1,2,6,5,4,1", "1,3,5,1,4", "1,3,5,1,4,5", "1,3,5,1,4,5,6", "1,3,5,1,4,5,6,2", "1,3,5,1,4,5,6,2,1", "1,3,5,4,1", "1,3,5,4,1,2", "1,3,5,4,1,2,6", "1,3,5,4,1,2,6,5", "1,3,5,4,1,2,6,5,1", "1,3,5,4,1,5", "1,3,5,4,1,5,6", "1,3,5,4,1,5,6,2", "1,3,5,4,1,5,6,2,1", "1,3,5,6,2,1", "1,3,5,6,2,1,4", "1,3,5,6,2,1,4,5", "1,3,5,6,2,1,4,5,1", "1,3,5,6,2,1,5", "1,3,5,6,2,1,5,4", "1,3,5,6,2,1,5,4,1", "1,4,5,1", "1,4,5,1,2", "1,4,5,1,2,6", "1,4,5,1,2,6,5", "1,4,5,1,2,6,5,3", "1,4,5,1,2,6,5,3,1", "1,4,5,1,3", "1,4,5,1,3,5", "1,4,5,1,3,5,6", "1,4,5,1,3,5,6,2", "1,4,5,1,3,5,6,2,1", "1,4,5,3,1", "1,4,5,3,1,2", "1,4,5,3,1,2,6", "1,4,5,3,1,2,6,5", "1,4,5,3,1,2,6,5,1", "1,4,5,3,1,5", "1,4,5,3,1,5,6", "1,4,5,3,1,5,6,2", "1,4,5,3,1,5,6,2,1", "1,4,5,6,2,1", "1,4,5,6,2,1,3", "1,4,5,6,2,1,3,5", "1,4,5,6,2,1,3,5,1", "1,4,5,6,2,1,5", "1,4,5,6,2,1,5,3", "1,4,5,6,2,1,5,3,1", "1,5,3,1", "1,5,3,1,2", "1,5,3,1,2,6", "1,5,3,1,2,6,5", "1,5,3,1,2,6,5,4", "1,5,3,1,2,6,5,4,1", "1,5,3,1,4", "1,5,3,1,4,5", "1,5,3,1,4,5,6", "1,5,3,1,4,5,6,2", "1,5,3,1,4,5,6,2,1", "1,5,4,1", "1,5,4,1,2", "1,5,4,1,2,6", "1,5,4,1,2,6,5", "1,5,4,1,2,6,5,3", "1,5,4,1,2,6,5,3,1", "1,5,4,1,3", "1,5,4,1,3,5", "1,5,4,1,3,5,6", "1,5,4,1,3,5,6,2", "1,5,4,1,3,5,6,2,1", "1,5,6,2,1", "1,5,6,2,1,3", "1,5,6,2,1,3,5", "1,5,6,2,1,3,5,4", "1,5,6,2,1,3,5,4,1", "1,5,6,2,1,4", "1,5,6,2,1,4,5", "1,5,6,2,1,4,5,3", "1,5,6,2,1,4,5,3,1" }; @BeforeClass public static void setup() { createGraph( THE_WORLD_AS_WE_KNOW_IT ); } @Test public void testSmallestPossibleInit() throws Exception { Traverser traversal = Traversal.description().traverse( referenceNode() ); int count = 0; for ( Path position : traversal ) { count++; assertNotNull( position ); assertNotNull( position.endNode() ); if ( position.length() > 0 ) { assertNotNull( position.lastRelationship() ); } assertNotNull( position.length() ); } assertFalse( "empty traversal", count == 0 ); } @Test public void testAllNodesAreReturnedOnceDepthFirst() throws Exception { testAllNodesAreReturnedOnce( Traversal.description().depthFirst() ); } @Test public void testAllNodesAreReturnedOnceBreadthFirst() throws Exception { testAllNodesAreReturnedOnce( Traversal.description().breadthFirst() ); } private void testAllNodesAreReturnedOnce( TraversalDescription traversal ) { Traverser traverser = traversal.uniqueness( Uniqueness.NODE_GLOBAL ).traverse( referenceNode() ); expectNodes( traverser, "1", "2", "3", "4", "5", "6" ); } @Test public void testNodesAreReturnedOnceWhenSufficientRecentlyUniqueDepthFirst() throws Exception { testNodesAreReturnedOnceWhenSufficientRecentlyUnique( Traversal.description().depthFirst() ); } @Test public void testNodesAreReturnedOnceWhenSufficientRecentlyUniqueBreadthFirst() throws Exception { testNodesAreReturnedOnceWhenSufficientRecentlyUnique( Traversal.description().breadthFirst() ); } private void testNodesAreReturnedOnceWhenSufficientRecentlyUnique( TraversalDescription description ) { Traverser traverser = description.uniqueness( Uniqueness.NODE_RECENT, 6 ).traverse( referenceNode() ); expectNodes( traverser, "1", "2", "3", "4", "5", "6" ); } @Test public void testAllRelationshipsAreReturnedOnceDepthFirst() throws Exception { testAllRelationshipsAreReturnedOnce( Traversal.description().depthFirst() ); } @Test public void testAllRelationshipsAreReturnedOnceBreadthFirst() throws Exception { testAllRelationshipsAreReturnedOnce( Traversal.description().breadthFirst() ); } private void testAllRelationshipsAreReturnedOnce( TraversalDescription description ) throws Exception { Traverser traverser = Traversal.description().uniqueness( Uniqueness.RELATIONSHIP_GLOBAL ).traverse( referenceNode() ); expectRelationships( traverser, THE_WORLD_AS_WE_KNOW_IT ); } @Test public void testRelationshipsAreReturnedOnceWhenSufficientRecentlyUniqueDepthFirst() throws Exception { testRelationshipsAreReturnedOnceWhenSufficientRecentlyUnique( Traversal.description().depthFirst() ); } @Test public void testRelationshipsAreReturnedOnceWhenSufficientRecentlyUniqueBreadthFirst() throws Exception { testRelationshipsAreReturnedOnceWhenSufficientRecentlyUnique( Traversal.description().breadthFirst() ); } private void testRelationshipsAreReturnedOnceWhenSufficientRecentlyUnique( TraversalDescription description ) throws Exception { Traverser traverser = description.uniqueness( Uniqueness.RELATIONSHIP_RECENT, THE_WORLD_AS_WE_KNOW_IT.length ).traverse( referenceNode() ); expectRelationships( traverser, THE_WORLD_AS_WE_KNOW_IT ); } @Test public void testAllUniqueNodePathsAreReturnedDepthFirst() throws Exception { testAllUniqueNodePathsAreReturned( Traversal.description().depthFirst() ); } @Test public void testAllUniqueNodePathsAreReturnedBreadthFirst() throws Exception { testAllUniqueNodePathsAreReturned( Traversal.description().breadthFirst() ); } private void testAllUniqueNodePathsAreReturned( TraversalDescription description ) throws Exception { Traverser traverser = description.uniqueness( Uniqueness.NODE_PATH ).traverse( referenceNode() ); expectPaths( traverser, NODE_UNIQUE_PATHS ); } @Test public void testAllUniqueRelationshipPathsAreReturnedDepthFirst() throws Exception { testAllUniqueRelationshipPathsAreReturned( Traversal.description().depthFirst() ); } @Test public void testAllUniqueRelationshipPathsAreReturnedBreadthFirst() throws Exception { testAllUniqueRelationshipPathsAreReturned( Traversal.description().breadthFirst() ); } private void testAllUniqueRelationshipPathsAreReturned( TraversalDescription description ) throws Exception { Set<String> expected = new HashSet<String>( Arrays.asList( NODE_UNIQUE_PATHS ) ); expected.addAll( Arrays.asList( RELATIONSHIP_UNIQUE_EXTRA_PATHS ) ); Traverser traverser = description.uniqueness( Uniqueness.RELATIONSHIP_PATH ).traverse( referenceNode() ); expectPaths( traverser, expected ); } @Test public void canPruneTraversalAtSpecificDepthDepthFirst() { canPruneTraversalAtSpecificDepth( Traversal.description().depthFirst() ); } @Test public void canPruneTraversalAtSpecificDepthBreadthFirst() { canPruneTraversalAtSpecificDepth( Traversal.description().breadthFirst() ); } private void canPruneTraversalAtSpecificDepth( TraversalDescription description ) { Traverser traverser = description.uniqueness( Uniqueness.NONE ).prune( Traversal.pruneAfterDepth( 1 ) ).traverse( referenceNode() ); expectNodes( traverser, "1", "2", "3", "4", "5" ); } @Test public void canPreFilterNodesDepthFirst() { canPreFilterNodes( Traversal.description().depthFirst() ); } @Test public void canPreFilterNodesBreadthFirst() { canPreFilterNodes( Traversal.description().breadthFirst() ); } private void canPreFilterNodes( TraversalDescription description ) { Traverser traverser = description.uniqueness( Uniqueness.NONE ).prune( Traversal.pruneAfterDepth( 2 ) ).filter( new Predicate<Path>() { public boolean accept( Path position ) { return position.length() == 2; } } ).traverse( referenceNode() ); expectPaths( traverser, "1,2,6", "1,3,5", "1,4,5", "1,5,3", "1,5,4", "1,5,6" ); } }