diff --git a/community/kernel-api/pom.xml b/community/kernel-api/pom.xml index 701a74dfa5dcd..dc85f82355310 100644 --- a/community/kernel-api/pom.xml +++ b/community/kernel-api/pom.xml @@ -65,6 +65,20 @@ the relevant Commercial Agreement. + + org.neo4j + neo4j-values + ${project.version} + test + test-jar + + + org.neo4j + neo4j-graphdb-api + ${project.version} + test + + junit junit @@ -80,13 +94,6 @@ the relevant Commercial Agreement. hamcrest-library test - - org.neo4j - neo4j-values - ${project.version} - test - test-jar - diff --git a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/KernelAPI.java b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/KernelAPI.java index 6f5855609badf..c5c1b0782bab2 100644 --- a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/KernelAPI.java +++ b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/KernelAPI.java @@ -20,24 +20,15 @@ package org.neo4j.internal.kernel.api; /** - * Entry point for the Cypher Kernel API + * Entry point for the Kernel API * - * Usage is roughly - * - * try ( PlannerFrame plannerFrame = kernelAPI.beginPlannerFrame() ) - * { - * LogicalPlan logicalPlan = Planner.plan( queryText, plannerFrame ); - * PhysicalPlan physicalPlan = PhysicalPlanner.specialize( logicalPlan, plannerFrame ); - * ExecutableQuery executableQuery = ExecutableMapping.map( physicalPlan, token ); - * - * try ( Runtime runtime = logicalPlan.beginRuntime() ) - * { - * executableQuery.execute( runtime ); - * } - * } + * Usage pattern is under development */ public interface KernelAPI { - PlannerFrame beginPlannerFrame(); + Transaction beginTransaction(); + + CursorFactory cursors(); + Token token(); } diff --git a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Read.java b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Read.java index 8149a69755c37..6b85bc398a9b5 100644 --- a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Read.java +++ b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Read.java @@ -73,7 +73,7 @@ public interface Read * @param nodeReference * a reference from {@link NodeCursor#nodeReference()}. * @param reference - * a reference from {@link NodeCursor#relationshipGroupReference()}. + * a reference from {@link NodeCursor#relationshipReference()}. * @param cursor * the cursor to use for consuming the results. */ diff --git a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Runtime.java b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Runtime.java index 112845d6cb582..c6e691cb66c1e 100644 --- a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Runtime.java +++ b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Runtime.java @@ -27,6 +27,7 @@ * Write provides all write operations * Schema provides all schema operations, meaning index and constraint creation and deletion */ +@Deprecated public interface Runtime extends AutoCloseable { CursorFactory cursorFactory(); diff --git a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Transaction.java b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Transaction.java index d82d2cbd29510..d191caa761d41 100644 --- a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Transaction.java +++ b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Transaction.java @@ -19,7 +19,8 @@ */ package org.neo4j.internal.kernel.api; -public interface Transaction extends Read, Write +public interface Transaction extends Read, Write, AutoCloseable { - + void success(); + void failure(); } diff --git a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Write.java b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Write.java index d2a7933cea800..7486f64d3ec10 100644 --- a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Write.java +++ b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Write.java @@ -24,7 +24,7 @@ /** * Defines the write operations of the Kernel API. */ -interface Write +public interface Write { long nodeCreate(); diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/DeepRelationshipTraversalCursorTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/DeepRelationshipTraversalCursorTestBase.java new file mode 100644 index 0000000000000..2d76977085e62 --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/DeepRelationshipTraversalCursorTestBase.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2002-2017 "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 . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.Test; + +import org.neo4j.collection.primitive.Primitive; +import org.neo4j.collection.primitive.PrimitiveLongSet; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.graphdb.Transaction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.neo4j.graphdb.RelationshipType.withName; + +public abstract class DeepRelationshipTraversalCursorTestBase + extends KernelAPIReadTestBase +{ + private static long three_root; + private static int expected_total, expected_unique; + + private RelationshipType PARENT = withName( "PARENT" ); + + @Override + void createTestGraph( GraphDatabaseService graphDb ) + { + try ( Transaction tx = graphDb.beginTx() ) + { + Node root = graphDb.createNode(); + three_root = root.getId(); + + Node[] leafs = new Node[32]; + for ( int i = 0; i < leafs.length; i++ ) + { + leafs[i] = graphDb.createNode(); + } + int offset = 0, duplicate = 12; + + Node interdup = graphDb.createNode(); + interdup.createRelationshipTo( root, PARENT ); + offset = relate( duplicate, leafs, offset, interdup ); + for ( int i = 0; i < 5; i++ ) + { + Node inter = graphDb.createNode(); + inter.createRelationshipTo( root, PARENT ); + offset = relate( 3 + i, leafs, offset, inter ); + } + interdup.createRelationshipTo( root, PARENT ); + for ( int i = 0; i < 4; i++ ) + { + Node inter = graphDb.createNode(); + inter.createRelationshipTo( root, PARENT ); + offset = relate( 2 + i, leafs, offset, inter ); + } + + Node inter = graphDb.createNode(); + inter.createRelationshipTo( root, PARENT ); + offset = relate( 1, leafs, offset, inter ); + + expected_total = offset + duplicate; + expected_unique = leafs.length; + + tx.success(); + } + } + + private int relate( int count, Node[] selection, int offset, Node parent ) + { + for ( int i = 0; i < count; i++ ) + { + selection[offset++ % selection.length].createRelationshipTo( parent, PARENT ); + } + return offset; + } + + @Test + public void shouldTraverseTreeOfDepthThree() throws Exception + { + try ( NodeCursor node = cursors.allocateNodeCursor(); + RelationshipGroupCursor group = cursors.allocateRelationshipGroupCursor(); + RelationshipTraversalCursor relationship1 = cursors.allocateRelationshipTraversalCursor(); + RelationshipTraversalCursor relationship2 = cursors.allocateRelationshipTraversalCursor(); + PrimitiveLongSet leafs = Primitive.longSet() ) + { + long total = 0; + + // when + read.singleNode( three_root, node ); + assertTrue( "access root node", node.next() ); + node.relationships( group ); + assertFalse( "single root", node.next() ); + + assertTrue( "access group of root", group.next() ); + group.incoming( relationship1 ); + assertFalse( "single group of root", group.next() ); + + while ( relationship1.next() ) + { + relationship1.neighbour( node ); + + assertTrue( "child level 1", node.next() ); + node.relationships( group ); + assertFalse( "single node", node.next() ); + + assertTrue( "group of level 1 child", group.next() ); + group.incoming( relationship2 ); + assertFalse( "single group of level 1 child", group.next() ); + + while ( relationship2.next() ) + { + leafs.add( relationship2.neighbourNodeReference() ); + total++; + } + } + + // then + assertEquals( "total number of leaf nodes", expected_total, total ); + assertEquals( "number of distinct leaf nodes", expected_unique, leafs.size() ); + } + } +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIReadTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIReadTestBase.java new file mode 100644 index 0000000000000..2c2d9b0722ee6 --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIReadTestBase.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.rules.TemporaryFolder; + +import java.io.IOException; + +import org.neo4j.graphdb.GraphDatabaseService; + +@SuppressWarnings( "WeakerAccess" ) +public abstract class KernelAPIReadTestBase +{ + protected static final TemporaryFolder folder = new TemporaryFolder(); + protected static KernelAPIReadTestSupport testSupport; + protected static KernelAPI kernel; + protected CursorFactory cursors; + protected Transaction read; + + /** + * Creates a new instance of KernelAPITestSupport, which will be used to execute the concrete test + */ + public abstract G newTestSupport(); + + /** + * Create the graph which all test in the class will be executed against. The graph is only built once, + * regardless of the number of tests. + * + * @param graphDb a graph API which should be used to build the test graph + */ + abstract void createTestGraph( GraphDatabaseService graphDb ); + + @Before + public void setupGraph() throws IOException + { + if ( testSupport == null ) + { + folder.create(); + testSupport = newTestSupport(); + testSupport.setup( folder.getRoot(), this::createTestGraph ); + kernel = testSupport.kernelToTest(); + } + testSupport.beforeEachTest(); + cursors = kernel.cursors(); + read = kernel.beginTransaction(); + } + + @After + public void closeTransaction() throws Exception + { + read.success(); + read.close(); + } + + @AfterClass + public static void tearDown() throws Exception + { + if ( testSupport != null ) + { + testSupport.tearDown(); + folder.delete(); + testSupport = null; + kernel = null; + } + } +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIReadTestSupport.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIReadTestSupport.java new file mode 100644 index 0000000000000..9b6a43501a720 --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIReadTestSupport.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.internal.kernel.api; + +import java.io.File; +import java.io.IOException; +import java.util.function.Consumer; + +import org.neo4j.graphdb.GraphDatabaseService; + +public interface KernelAPIReadTestSupport +{ + void setup( File storeDir, Consumer create ) throws IOException; + + void beforeEachTest(); + + KernelAPI kernelToTest(); + + void tearDown(); +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIWriteTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIWriteTestBase.java new file mode 100644 index 0000000000000..ba15c4d5f25e3 --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIWriteTestBase.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.rules.TemporaryFolder; + +import java.io.IOException; + +import org.neo4j.graphdb.GraphDatabaseService; + +@SuppressWarnings( "WeakerAccess" ) +public abstract class KernelAPIWriteTestBase +{ + protected static final TemporaryFolder folder = new TemporaryFolder(); + protected static KernelAPIWriteTestSupport testSupport; + protected static KernelAPI kernel; + protected static GraphDatabaseService graphDb; + + /** + * Creates a new instance of KernelAPITestSupport, which will be used to execute the concrete test + */ + public abstract G newTestSupport(); + + @Before + public void setupGraph() throws IOException + { + if ( testSupport == null ) + { + folder.create(); + testSupport = newTestSupport(); + testSupport.setup( folder.getRoot() ); + kernel = testSupport.kernelToTest(); + graphDb = testSupport.graphBackdoor(); + } + testSupport.beforeEachTest(); + } + + @AfterClass + public static void tearDown() throws Exception + { + if ( testSupport != null ) + { + testSupport.tearDown(); + folder.delete(); + testSupport = null; + kernel = null; + } + } +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIWriteTestSupport.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIWriteTestSupport.java new file mode 100644 index 0000000000000..dc116b3a3abe1 --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/KernelAPIWriteTestSupport.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.internal.kernel.api; + +import java.io.File; +import java.io.IOException; + +import org.neo4j.graphdb.GraphDatabaseService; + +public interface KernelAPIWriteTestSupport +{ + void setup( File storeDir ) throws IOException; + + void beforeEachTest(); + + KernelAPI kernelToTest(); + + /** + * Backdoor to allow asserting on write effects + */ + GraphDatabaseService graphBackdoor(); + + void tearDown(); +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/LargeNodeCursorTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/LargeNodeCursorTestBase.java new file mode 100644 index 0000000000000..92319fdd27f6c --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/LargeNodeCursorTestBase.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public abstract class LargeNodeCursorTestBase extends KernelAPIReadTestBase +{ + private static List NODE_IDS = new ArrayList<>(); + private static int N_NODES = 10000; + + private static Random random = new Random( 2 ); + + void createTestGraph( GraphDatabaseService graphDb ) + { + List deleted = new ArrayList<>(); + try ( Transaction tx = graphDb.beginTx() ) + { + for ( int i = 0; i < N_NODES; i++ ) + { + Node node = graphDb.createNode(); + if ( random.nextBoolean() ) + { + NODE_IDS.add( node.getId() ); + } + else + { + deleted.add( node ); + } + } + tx.success(); + } + + try ( Transaction tx = graphDb.beginTx() ) + { + for ( Node node : deleted ) + { + node.delete(); + } + tx.success(); + } + } + + @Test + public void shouldScanNodes() throws Exception + { + // given + List ids = new ArrayList<>(); + try ( NodeCursor nodes = cursors.allocateNodeCursor() ) + { + // when + read.allNodesScan( nodes ); + while ( nodes.next() ) + { + ids.add( nodes.nodeReference() ); + } + } + + // then + assertEquals( NODE_IDS, ids ); + } + + @Test + public void shouldAccessNodesByReference() throws Exception + { + // given + try ( NodeCursor nodes = cursors.allocateNodeCursor() ) + { + for ( long id : NODE_IDS ) + { + // when + read.singleNode( id, nodes ); + + // then + assertTrue( "should access defined node", nodes.next() ); + assertEquals( "should access the correct node", id, nodes.nodeReference() ); + assertFalse( "should only access a single node", nodes.next() ); + } + } + } +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/LowLevelPropertyCursorTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/LowLevelPropertyCursorTestBase.java new file mode 100644 index 0000000000000..497eda7567b55 --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/LowLevelPropertyCursorTestBase.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.neo4j.function.ThrowingAction; +import org.neo4j.values.storable.BufferValueWriter; +import org.neo4j.values.storable.Values; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public abstract class LowLevelPropertyCursorTestBase +{ + private static final int NO_SUCH_PROPERTY = -1; + + abstract PropertyCursor emptyCursor(); + abstract PropertyCursor withValues( Map values ); + + private BufferValueWriter writeBuffer = new BufferValueWriter(); + + @Test + public void shouldBeEmpty() + { + assertEmpty( emptyCursor() ); + assertEmpty( withValues( Collections.emptyMap() ) ); + } + + private void assertEmpty( PropertyCursor cursor ) + { + for ( int i = 0; i < 2; i++ ) + { + assertFalse( cursor.next() ); + + assertThat( cursor.propertyKey(), equalTo( NO_SUCH_PROPERTY ) ); + assertThat( cursor.propertyValue(), equalTo( Values.NO_VALUE ) ); + + assertException( cursor::booleanValue ); + assertException( cursor::stringValue ); + assertException( cursor::longValue ); + assertException( cursor::doubleValue ); + + // Should we wait with specialized predicates? +// assertFalse( cursor.valueEqualTo( 0L ) ); +// assertFalse( cursor.valueEqualTo( 0.0 ) ); +// assertFalse( cursor.valueEqualTo( "" ) ); +// assertFalse( cursor.valueMatches( Pattern.compile( ".*" ) ) ); +// +// assertFalse( cursor.valueGreaterThan( 0L ) ); +// assertFalse( cursor.valueGreaterThan( 0.0 ) ); +// assertFalse( cursor.valueLessThan( 0L ) ); +// assertFalse( cursor.valueLessThan( 0.0 ) ); +// assertFalse( cursor.valueGreaterThanOrEqualTo( 0L ) ); +// assertFalse( cursor.valueGreaterThanOrEqualTo( 0.0 ) ); +// assertFalse( cursor.valueLessThanOrEqualTo( 0L ) ); +// assertFalse( cursor.valueLessThanOrEqualTo( 0.0 ) ); + } + } + + @Test + public void shouldParseString() + { + String X = "hi"; + Map map = new HashMap<>(); + map.put( 1, X ); + PropertyCursor cursor = withValues( map ); + + assertTrue( cursor.next() ); + assertThat( cursor.propertyKey(), equalTo( 1 ) ); + assertThat( cursor.stringValue(), equalTo( X ) ); + assertTrue( cursor.valueEqualTo( X ) ); + + assertException( cursor::booleanValue ); + assertException( cursor::longValue ); + assertException( cursor::doubleValue ); + + cursor.writeTo( writeBuffer ); + writeBuffer.assertBuffer( X ); + + assertEmpty( cursor ); + } + + @Test + public void shouldParseInteger() + { + long X = 2L; + Map map = new HashMap<>(); + map.put( 1, X ); + PropertyCursor cursor = withValues( map ); + + assertTrue( cursor.next() ); + assertThat( cursor.propertyKey(), equalTo( 1 ) ); + assertThat( cursor.stringValue(), equalTo( X ) ); + assertTrue( cursor.valueEqualTo( X ) ); + + assertException( cursor::booleanValue ); + assertException( cursor::stringValue ); + assertException( cursor::doubleValue ); + + cursor.writeTo( writeBuffer ); + writeBuffer.assertBuffer( X ); + + assertEmpty( cursor ); + } + + @Test + public void shouldParseFloat() + { + double X = 2.0; + Map map = new HashMap<>(); + map.put( 1, X ); + PropertyCursor cursor = withValues( map ); + + assertTrue( cursor.next() ); + assertThat( cursor.propertyKey(), equalTo( 1 ) ); + assertThat( cursor.stringValue(), equalTo( X ) ); + assertTrue( cursor.valueEqualTo( X ) ); + + assertException( cursor::booleanValue ); + assertException( cursor::stringValue ); + assertException( cursor::longValue ); + + cursor.writeTo( writeBuffer ); + writeBuffer.assertBuffer( X ); + + assertEmpty( cursor ); + } + + @SuppressWarnings( "ConstantConditions" ) + @Test + public void shouldParseBoolean() + { + boolean X = false; + Map map = new HashMap<>(); + map.put( 1, X ); + PropertyCursor cursor = withValues( map ); + + assertTrue( cursor.next() ); + assertThat( cursor.propertyKey(), equalTo( 1 ) ); + assertThat( cursor.stringValue(), equalTo( X ) ); + + assertException( cursor::doubleValue ); + assertException( cursor::stringValue ); + assertException( cursor::longValue ); + + cursor.writeTo( writeBuffer ); + writeBuffer.assertBuffer( X ); + + assertEmpty( cursor ); + } + + private void assertException( ThrowingAction action ) + { + try + { + action.apply(); + fail( "Expected exception" ); + } + catch ( Exception e ) + { + // IGNORE + } + } +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeCursorTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeCursorTestBase.java new file mode 100644 index 0000000000000..929c7ed4550a1 --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeCursorTestBase.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.neo4j.graphdb.Label.label; + +public abstract class NodeCursorTestBase extends KernelAPIReadTestBase +{ + private static List NODE_IDS; + private static long foo, bar, baz, barbaz, bare, gone; + + void createTestGraph( GraphDatabaseService graphDb ) + { + Node deleted; + try ( Transaction tx = graphDb.beginTx() ) + { + foo = graphDb.createNode( label( "Foo" ) ).getId(); + bar = graphDb.createNode( label( "Bar" ) ).getId(); + baz = graphDb.createNode( label( "Baz" ) ).getId(); + barbaz = graphDb.createNode( label( "Bar" ), label( "Baz" ) ).getId(); + gone = (deleted = graphDb.createNode()).getId(); + bare = graphDb.createNode().getId(); + + tx.success(); + } + + try ( Transaction tx = graphDb.beginTx() ) + { + deleted.delete(); + + tx.success(); + } + + try ( Transaction tx = graphDb.beginTx() ) + { + NODE_IDS = new ArrayList<>(); + for ( Node node : graphDb.getAllNodes() ) + { + NODE_IDS.add( node.getId() ); + } + tx.success(); + } + } + + @Test + public void shouldScanNodes() throws Exception + { + // given + List ids = new ArrayList<>(); + try ( NodeCursor nodes = cursors.allocateNodeCursor() ) + { + // when + read.allNodesScan( nodes ); + while ( nodes.next() ) + { + ids.add( nodes.nodeReference() ); + } + } + + // then + assertEquals( NODE_IDS, ids ); + } + + @Test + public void shouldAccessNodesByReference() throws Exception + { + // given + try ( NodeCursor nodes = cursors.allocateNodeCursor() ) + { + for ( long id : NODE_IDS ) + { + // when + read.singleNode( id, nodes ); + + // then + assertTrue( "should access defined node", nodes.next() ); + assertEquals( "should access the correct node", id, nodes.nodeReference() ); + assertFalse( "should only access a single node", nodes.next() ); + } + } + } + + @Test + public void shouldNotFindDeletedNode() throws Exception + { + // given + try ( NodeCursor nodes = cursors.allocateNodeCursor() ) + { + // when + read.singleNode( gone, nodes ); + + // then + assertFalse( "should not access deleted node", nodes.next() ); + } + } + + @Test + public void shouldReadLabels() throws Exception + { + // given + try ( NodeCursor nodes = cursors.allocateNodeCursor() ) + { + LabelSet labels; + + // when + read.singleNode( foo, nodes ); + + // then + assertTrue( "should access defined node", nodes.next() ); + labels = nodes.labels(); + assertEquals( "number of labels", 1, labels.numberOfLabels() ); + int _foo = labels.label( 0 ); + assertFalse( "should only access a single node", nodes.next() ); + + // when + read.singleNode( bar, nodes ); + + // then + assertTrue( "should access defined node", nodes.next() ); + labels = nodes.labels(); + assertEquals( "number of labels", 1, labels.numberOfLabels() ); + int _bar = labels.label( 0 ); + assertFalse( "should only access a single node", nodes.next() ); + + // when + read.singleNode( baz, nodes ); + + // then + assertTrue( "should access defined node", nodes.next() ); + labels = nodes.labels(); + assertEquals( "number of labels", 1, labels.numberOfLabels() ); + int _baz = labels.label( 0 ); + assertFalse( "should only access a single node", nodes.next() ); + + assertNotEquals( "distinct labels", _foo, _bar ); + assertNotEquals( "distinct labels", _foo, _baz ); + assertNotEquals( "distinct labels", _bar, _baz ); + + // when + read.singleNode( barbaz, nodes ); + + // then + assertTrue( "should access defined node", nodes.next() ); + labels = nodes.labels(); + assertEquals( "number of labels", 2, labels.numberOfLabels() ); + if ( labels.label( 0 ) == _bar ) + { + assertEquals( _baz, labels.label( 1 ) ); + } + else + { + assertEquals( _baz, labels.label( 0 ) ); + assertEquals( _bar, labels.label( 1 ) ); + } + assertFalse( "should only access a single node", nodes.next() ); + + // when + read.singleNode( bare, nodes ); + + // then + assertTrue( "should access defined node", nodes.next() ); + labels = nodes.labels(); + assertEquals( "number of labels", 0, labels.numberOfLabels() ); + assertFalse( "should only access a single node", nodes.next() ); + } + } +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeWriteTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeWriteTestBase.java new file mode 100644 index 0000000000000..e9ad6b52b81f9 --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeWriteTestBase.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.Test; + +import org.neo4j.graphdb.NotFoundException; +import org.neo4j.helpers.collection.Iterables; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.neo4j.graphdb.Label.label; + +public abstract class NodeWriteTestBase extends KernelAPIWriteTestBase +{ + @Test + public void shouldCreateNode() throws Exception + { + long node; + try ( Transaction tx = kernel.beginTransaction() ) + { + node = tx.nodeCreate(); + tx.success(); + } + + try ( org.neo4j.graphdb.Transaction tx = graphDb.beginTx() ) + { + assertEquals( node, graphDb.getNodeById( node ).getId() ); + } + } + + @Test + public void shouldRollbackOnFailure() throws Exception + { + long node; + try ( Transaction tx = kernel.beginTransaction() ) + { + node = tx.nodeCreate(); + tx.failure(); + } + + try ( org.neo4j.graphdb.Transaction tx = graphDb.beginTx() ) + { + graphDb.getNodeById( node ); + fail( "There should be no node" ); + } + catch ( NotFoundException e ) + { + // expected + } + } + + @Test + public void shouldAddLabelNode() throws Exception + { + long node; + int labelId; + final String labelName = "Town"; + try ( Transaction tx = kernel.beginTransaction() ) + { + node = tx.nodeCreate(); + labelId = kernel.token().labelGetOrCreateForName( labelName ); + tx.nodeAddLabel( node, labelId ); + tx.success(); + } + + try ( org.neo4j.graphdb.Transaction tx = graphDb.beginTx() ) + { + assertThat( + graphDb.getNodeById( node ).getLabels(), + equalTo( Iterables.iterable( label( labelName ) ) ) ); + } + } + + @Test + public void shouldRemoveLabel() throws Exception + { + long nodeId; + int labelId; + final String labelName = "Town"; + + try ( org.neo4j.graphdb.Transaction tx = graphDb.beginTx() ) + { + nodeId = graphDb.createNode( label( labelName ) ).getId(); + tx.success(); + } + + try ( Transaction tx = kernel.beginTransaction() ) + { + labelId = kernel.token().labelGetOrCreateForName( labelName ); + tx.nodeRemoveLabel( nodeId, labelId ); + tx.success(); + } + + try ( org.neo4j.graphdb.Transaction tx = graphDb.beginTx() ) + { + assertThat( + graphDb.getNodeById( nodeId ).getLabels(), + equalTo( Iterables.empty() ) ); + } + } +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/PropertyCursorTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/PropertyCursorTestBase.java index 4686a528681aa..846d7d5e97302 100644 --- a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/PropertyCursorTestBase.java +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/PropertyCursorTestBase.java @@ -5,185 +5,279 @@ * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * 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 General Public License for more details. + * GNU Affero General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ package org.neo4j.internal.kernel.api; -import org.junit.Test; +import java.util.HashSet; +import java.util.Set; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.junit.Test; -import org.neo4j.function.ThrowingAction; -import org.neo4j.values.storable.BufferValueWriter; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; import org.neo4j.values.storable.Values; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.hasItem; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -public abstract class PropertyCursorTestBase +public abstract class PropertyCursorTestBase extends KernelAPIReadTestBase { - private static final int NO_SUCH_PROPERTY = -1; - - abstract PropertyCursor emptyCursor(); - abstract PropertyCursor withValues( Map values ); - - private BufferValueWriter writeBuffer = new BufferValueWriter(); - - @Test - public void shouldBeEmpty() - { - assertEmpty( emptyCursor() ); - assertEmpty( withValues( Collections.emptyMap() ) ); - } - - private void assertEmpty( PropertyCursor cursor ) + private static final String LONG_STRING = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque " + + "eget nibh cursus, efficitur risus non, ultrices justo. Nulla laoreet eros mi, non molestie magna " + + "luctus in. Fusce nibh neque, tristique ultrices laoreet et, aliquet non dolor. Donec ultrices nisi " + + "eget urna luctus volutpat. Vivamus hendrerit eget justo vel scelerisque. Morbi interdum volutpat diam," + + " et cursus arcu efficitur consectetur. Cras vitae facilisis ipsum, vitae ullamcorper orci. Nullam " + + "tristique ante sed nibh consequat posuere. Curabitur mauris nisl, condimentum ac varius vel, imperdiet" + + " a neque. Sed euismod condimentum nisl, vel efficitur turpis tempus id.\n" + + "\n" + + "Sed in tempor arcu. Suspendisse molestie rutrum risus a dignissim. Donec et orci non diam tincidunt " + + "sollicitudin non id nisi. Aliquam vehicula imperdiet viverra. Cras et lacinia eros. Etiam imperdiet ac" + + " dolor ut tristique. Phasellus ut lacinia ex. Pellentesque habitant morbi tristique senectus et netus " + + "et malesuada fames ac turpis egestas. Integer libero justo, tincidunt ut felis non, interdum " + + "consectetur mauris. Cras eu felis ante. Sed dapibus nulla urna, at elementum tortor ultricies pretium." + + " Maecenas sed augue non urna consectetur fringilla vitae eu libero. Vivamus interdum bibendum risus, " + + "quis luctus eros.\n" + + "\n" + + "Sed neque augue, fermentum sit amet iaculis ut, porttitor ac odio. Phasellus et sapien non sapien " + + "consequat fermentum accumsan non dolor. Integer eget pellentesque lectus, vitae lobortis ante. Nam " + + "elementum, dui ut finibus rutrum, purus mauris efficitur purus, efficitur tempus ante metus bibendum " + + "velit. Curabitur commodo, risus et eleifend facilisis, eros augue posuere tortor, eu dictum erat " + + "tortor consectetur orci. Fusce a velit dignissim, tempus libero nec, faucibus risus. Nullam pharetra " + + "mauris sit amet volutpat facilisis. Pellentesque habitant morbi tristique senectus et netus et " + + "malesuada fames ac turpis egestas. Praesent lacinia non felis ut lobortis.\n" + + "\n" + + "Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed eu nisi dui" + + ". Suspendisse imperdiet lorem vel eleifend faucibus. Mauris non venenatis metus. Aenean neque magna, " + + "rhoncus vel velit in, dictum convallis leo. Phasellus pulvinar eu sapien ac vehicula. Praesent " + + "placerat augue quam, egestas vehicula velit porttitor in. Vivamus velit metus, pellentesque quis " + + "fermentum et, porta quis velit. Curabitur sed lacus quis nibh convallis tincidunt.\n" + + "\n" + + "Etiam eu elit eget dolor dignissim lacinia. Vivamus tortor ex, dapibus id elementum non, suscipit ac " + + "nisl. Aenean vel tempor libero, eu venenatis elit. Nunc nec velit eu odio interdum pellentesque sed et" + + " eros. Nam quis mi in metus tristique aliquam. Nullam facilisis dapibus lacus, nec lacinia velit. " + + "Proin massa enim, accumsan ac libero at, iaculis sodales tellus. Vivamus fringilla justo sed luctus " + + "tincidunt. Sed placerat fringilla ex, vel placerat sem faucibus eget. Vestibulum semper dui sit amet " + + "efficitur blandit. Donec eu tellus velit. Etiam a mi nec massa euismod posuere. Cras eget lacus leo."; + private static long bare, byteProp, shortProp, intProp, inlineLongProp, longProp, + floatProp, doubleProp, trueProp, falseProp, charProp, emptyStringProp, shortStringProp, longStringProp, + utf8Prop, smallArray, bigArray, allProps; + private static String chinese = "造Unicode之"; + + @Override + void createTestGraph( GraphDatabaseService graphDb ) { - for ( int i = 0; i < 2; i++ ) + try ( Transaction tx = graphDb.beginTx() ) { - assertFalse( cursor.next() ); - - assertThat( cursor.propertyKey(), equalTo( NO_SUCH_PROPERTY ) ); - assertThat( cursor.propertyValue(), equalTo( Values.NO_VALUE ) ); - - assertException( cursor::booleanValue ); - assertException( cursor::stringValue ); - assertException( cursor::longValue ); - assertException( cursor::doubleValue ); - - // Should we wait with specialized predicates? -// assertFalse( cursor.valueEqualTo( 0L ) ); -// assertFalse( cursor.valueEqualTo( 0.0 ) ); -// assertFalse( cursor.valueEqualTo( "" ) ); -// assertFalse( cursor.valueMatches( Pattern.compile( ".*" ) ) ); -// -// assertFalse( cursor.valueGreaterThan( 0L ) ); -// assertFalse( cursor.valueGreaterThan( 0.0 ) ); -// assertFalse( cursor.valueLessThan( 0L ) ); -// assertFalse( cursor.valueLessThan( 0.0 ) ); -// assertFalse( cursor.valueGreaterThanOrEqualTo( 0L ) ); -// assertFalse( cursor.valueGreaterThanOrEqualTo( 0.0 ) ); -// assertFalse( cursor.valueLessThanOrEqualTo( 0L ) ); -// assertFalse( cursor.valueLessThanOrEqualTo( 0.0 ) ); + bare = graphDb.createNode().getId(); + + byteProp = createNodeWithProperty( graphDb, "byteProp", (byte) 13 ); + shortProp = createNodeWithProperty( graphDb, "shortProp", (short) 13 ); + intProp = createNodeWithProperty( graphDb, "intProp", 13 ); + inlineLongProp = createNodeWithProperty( graphDb, "inlineLongProp", 13L ); + longProp = createNodeWithProperty( graphDb, "longProp", Long.MAX_VALUE ); + + floatProp = createNodeWithProperty( graphDb, "floatProp", 13.0f ); + doubleProp = createNodeWithProperty( graphDb, "doubleProp", 13.0 ); + + trueProp = createNodeWithProperty( graphDb, "trueProp", true ); + falseProp = createNodeWithProperty( graphDb, "falseProp", false ); + + charProp = createNodeWithProperty( graphDb, "charProp", 'x' ); + emptyStringProp = createNodeWithProperty( graphDb, "emptyStringProp", "" ); + shortStringProp = createNodeWithProperty( graphDb, "shortStringProp", "hello" ); + longStringProp = createNodeWithProperty( graphDb, "longStringProp", LONG_STRING ); + utf8Prop = createNodeWithProperty( graphDb, "utf8Prop", chinese ); + + smallArray = createNodeWithProperty( graphDb, "smallArray", new int[] {1, 2, 3, 4} ); + bigArray = createNodeWithProperty( graphDb, "bigArray", new String[] {LONG_STRING} ); + + Node all = graphDb.createNode(); + // first property record + all.setProperty( "byteProp", (byte) 13 ); + all.setProperty( "shortProp", (short) 13 ); + all.setProperty( "intProp", 13 ); + all.setProperty( "inlineLongProp", 13L ); + // second property record + all.setProperty( "longProp", Long.MAX_VALUE ); + all.setProperty( "floatProp", 13.0f ); + all.setProperty( "doubleProp", 13.0 ); + // ^^^ + // third property record halfway through double? + all.setProperty( "trueProp", true ); + all.setProperty( "falseProp", false ); + + all.setProperty( "charProp", 'x' ); + all.setProperty( "emptyStringProp", "" ); + all.setProperty( "shortStringProp", "hello" ); + all.setProperty( "longStringProp", LONG_STRING ); + all.setProperty( "utf8Prop", chinese ); + + all.setProperty( "smallArray", new int[] {1, 2, 3, 4} ); + all.setProperty( "bigArray", new String[] {LONG_STRING} ); + + allProps = all.getId(); + + tx.success(); } } - @Test - public void shouldParseString() + private long createNodeWithProperty( GraphDatabaseService graphDb, String propertyKey, Object value ) { - String X = "hi"; - Map map = new HashMap<>(); - map.put( 1, X ); - PropertyCursor cursor = withValues( map ); - - assertTrue( cursor.next() ); - assertThat( cursor.propertyKey(), equalTo( 1 ) ); - assertThat( cursor.stringValue(), equalTo( X ) ); - assertTrue( cursor.valueEqualTo( X ) ); - - assertException( cursor::booleanValue ); - assertException( cursor::longValue ); - assertException( cursor::doubleValue ); - - cursor.writeTo( writeBuffer ); - writeBuffer.assertBuffer( X ); - - assertEmpty( cursor ); + Node p = graphDb.createNode(); + p.setProperty( propertyKey, value ); + return p.getId(); } @Test - public void shouldParseInteger() + public void shouldNotAccessNonExistentProperties() throws Exception { - long X = 2L; - Map map = new HashMap<>(); - map.put( 1, X ); - PropertyCursor cursor = withValues( map ); - - assertTrue( cursor.next() ); - assertThat( cursor.propertyKey(), equalTo( 1 ) ); - assertThat( cursor.stringValue(), equalTo( X ) ); - assertTrue( cursor.valueEqualTo( X ) ); + // given + try ( NodeCursor node = cursors.allocateNodeCursor(); + PropertyCursor props = cursors.allocatePropertyCursor() ) + { + // when + read.singleNode( bare, node ); + assertTrue( "node by reference", node.next() ); + assertFalse( "no properties", node.hasProperties() ); - assertException( cursor::booleanValue ); - assertException( cursor::stringValue ); - assertException( cursor::doubleValue ); + node.properties( props ); + assertFalse( "no properties by direct method", props.next() ); - cursor.writeTo( writeBuffer ); - writeBuffer.assertBuffer( X ); + read.nodeProperties( node.propertiesReference(), props ); + assertFalse( "no properties via property ref", props.next() ); - assertEmpty( cursor ); + assertFalse( "only one node", node.next() ); + } } @Test - public void shouldParseFloat() + public void shouldAccessSingleProperty() throws Exception { - double X = 2.0; - Map map = new HashMap<>(); - map.put( 1, X ); - PropertyCursor cursor = withValues( map ); - - assertTrue( cursor.next() ); - assertThat( cursor.propertyKey(), equalTo( 1 ) ); - assertThat( cursor.stringValue(), equalTo( X ) ); - assertTrue( cursor.valueEqualTo( X ) ); - - assertException( cursor::booleanValue ); - assertException( cursor::stringValue ); - assertException( cursor::longValue ); - - cursor.writeTo( writeBuffer ); - writeBuffer.assertBuffer( X ); - - assertEmpty( cursor ); + assertAccessSingleProperty( byteProp, Values.of( (byte) 13 ) ); + assertAccessSingleProperty( shortProp, Values.of( (short) 13 ) ); + assertAccessSingleProperty( intProp, Values.of( 13 ) ); + assertAccessSingleProperty( inlineLongProp, Values.of( 13L ) ); + assertAccessSingleProperty( longProp, Values.of( Long.MAX_VALUE ) ); + assertAccessSingleProperty( floatProp, Values.of( 13.0f ) ); + assertAccessSingleProperty( doubleProp, Values.of( 13.0 ) ); + assertAccessSingleProperty( trueProp, Values.of( true ) ); + assertAccessSingleProperty( falseProp, Values.of( false ) ); + assertAccessSingleProperty( charProp, Values.of( 'x' ) ); + assertAccessSingleProperty( emptyStringProp, Values.of( "" ) ); + assertAccessSingleProperty( shortStringProp, Values.of( "hello" ) ); + assertAccessSingleProperty( longStringProp, Values.of( LONG_STRING ) ); + assertAccessSingleProperty( utf8Prop, Values.of( chinese ) ); + assertAccessSingleProperty( smallArray, Values.of( new int[] {1, 2, 3, 4} ) ); + assertAccessSingleProperty( bigArray, Values.of( new String[] {LONG_STRING} ) ); } - @SuppressWarnings( "ConstantConditions" ) @Test - public void shouldParseBoolean() + public void shouldAccessAllNodeProperties() throws Exception { - boolean X = false; - Map map = new HashMap<>(); - map.put( 1, X ); - PropertyCursor cursor = withValues( map ); - - assertTrue( cursor.next() ); - assertThat( cursor.propertyKey(), equalTo( 1 ) ); - assertThat( cursor.stringValue(), equalTo( X ) ); - - assertException( cursor::doubleValue ); - assertException( cursor::stringValue ); - assertException( cursor::longValue ); - - cursor.writeTo( writeBuffer ); - writeBuffer.assertBuffer( X ); - - assertEmpty( cursor ); + // given + try ( NodeCursor node = cursors.allocateNodeCursor(); + PropertyCursor props = cursors.allocatePropertyCursor() ) + { + // when + read.singleNode( allProps, node ); + assertTrue( "node by reference", node.next() ); + assertTrue( "has properties", node.hasProperties() ); + + node.properties( props ); + Set values = new HashSet<>(); + while ( props.next() ) + { + values.add( props.propertyValue().asObject() ); + } + + assertTrue( "byteProp", values.contains( (byte) 13 ) ); + assertTrue( "shortProp", values.contains( (short) 13 ) ); + assertTrue( "intProp", values.contains( 13 ) ); + assertTrue( "inlineLongProp", values.contains( 13L ) ); + assertTrue( "longProp", values.contains( Long.MAX_VALUE ) ); + assertTrue( "floatProp", values.contains( 13.0f ) ); + assertTrue( "doubleProp", values.contains( 13.0 ) ); + assertTrue( "trueProp", values.contains( true ) ); + assertTrue( "falseProp", values.contains( false ) ); + assertTrue( "charProp", values.contains( 'x' ) ); + assertTrue( "emptyStringProp", values.contains( "" ) ); + assertTrue( "shortStringProp", values.contains( "hello" ) ); + assertTrue( "longStringProp", values.contains( LONG_STRING ) ); + assertTrue( "utf8Prop", values.contains( chinese ) ); + assertThat( "smallArray", values, hasItem( intArray( 1, 2, 3, 4 ) ) ); + assertThat( "bigArray", values, hasItem( arrayContaining( LONG_STRING ) ) ); + + assertEquals( "number of values", 16, values.size() ); + } } - private void assertException( ThrowingAction action ) + private void assertAccessSingleProperty( long nodeId, Object expectedValue ) { - try + // given + try ( NodeCursor node = cursors.allocateNodeCursor(); + PropertyCursor props = cursors.allocatePropertyCursor() ) { - action.apply(); - fail( "Expected exception" ); + // when + read.singleNode( nodeId, node ); + assertTrue( "node by reference", node.next() ); + assertTrue( "has properties", node.hasProperties() ); + + node.properties( props ); + assertTrue( "has properties by direct method", props.next() ); + assertEquals( "correct value", expectedValue, props.propertyValue() ); + assertFalse( "single property", props.next() ); + + read.nodeProperties( node.propertiesReference(), props ); + assertTrue( "has properties via property ref", props.next() ); + assertEquals( "correct value", expectedValue, props.propertyValue() ); + assertFalse( "single property", props.next() ); } - catch ( Exception e ) + } + + private static TypeSafeMatcher intArray( int... content ) + { + return new TypeSafeMatcher() { - // IGNORE - } + @Override + protected boolean matchesSafely( int[] item ) + { + if ( item.length != content.length ) + { + return false; + } + for ( int i = 0; i < content.length; i++ ) + { + if ( item[i] != content[i] ) + { + return false; + } + } + return true; + } + + @Override + public void describeTo( Description description ) + { + description.appendValue( content ); + } + }; } } diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RandomRelationshipTraversalCursorTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RandomRelationshipTraversalCursorTestBase.java new file mode 100644 index 0000000000000..7d4b8528f5711 --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RandomRelationshipTraversalCursorTestBase.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2002-2017 "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 . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Label; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.graphdb.Transaction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public abstract class RandomRelationshipTraversalCursorTestBase + extends KernelAPIReadTestBase +{ + private static final int N_TRAVERSALS = 10_000; + private static int N_NODES = 100; + private static int N_RELATIONSHIPS = 1000; + private static Random random = new Random( 666 ); + private static List nodeIds = new ArrayList<>(); + + @Override + void createTestGraph( GraphDatabaseService graphDb ) + { + try ( Transaction tx = graphDb.beginTx() ) + { + for ( int i = 0; i < N_NODES; i++ ) + { + nodeIds.add( graphDb.createNode( Label.label( "LABEL" + i ) ).getId() ); + } + tx.success(); + } + + try ( Transaction tx = graphDb.beginTx() ) + { + for ( int i = 0; i < N_RELATIONSHIPS; i++ ) + { + Long source = nodeIds.get( random.nextInt( N_NODES ) ); + Long target = nodeIds.get( random.nextInt( N_NODES ) ); + graphDb.getNodeById( source ).createRelationshipTo( graphDb.getNodeById( target ), + RelationshipType.withName( "REL" + (i % 10) ) ); + } + tx.success(); + } + } + + @Test + public void shouldManageRandomTraversals() throws Exception + { + // given + try ( NodeCursor node = cursors.allocateNodeCursor(); + RelationshipGroupCursor group = cursors.allocateRelationshipGroupCursor(); + RelationshipTraversalCursor relationship = cursors.allocateRelationshipTraversalCursor() ) + { + for ( int i = 0; i < N_TRAVERSALS; i++ ) + { + // when + long nodeId = nodeIds.get( random.nextInt( N_NODES ) ); + read.singleNode( nodeId, node ); + assertTrue( "access root node", node.next() ); + node.relationships( group ); + assertFalse( "single root", node.next() ); + + // then + while ( group.next() ) + { + group.incoming( relationship ); + while ( relationship.next() ) + { + assertEquals( "incoming origin", nodeId, relationship.originNodeReference() ); + relationship.neighbour( node ); + } + group.outgoing( relationship ); + while ( relationship.next() ) + { + assertEquals( "outgoing origin", nodeId, relationship.originNodeReference() ); + relationship.neighbour( node ); + } + group.loops( relationship ); + while ( relationship.next() ) + { + assertEquals( "loop origin", nodeId, relationship.originNodeReference() ); + relationship.neighbour( node ); + } + } + } + } + } +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipScanCursorTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipScanCursorTestBase.java new file mode 100644 index 0000000000000..d3499ad9ea22f --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipScanCursorTestBase.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2002-2017 "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 . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.Transaction; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.neo4j.graphdb.RelationshipType.withName; + +public abstract class RelationshipScanCursorTestBase extends KernelAPIReadTestBase +{ + private static List RELATIONSHIP_IDS; + private static long none, loop, one, c, d; + + @Override + void createTestGraph( GraphDatabaseService graphDb ) + { + Relationship deleted; + try ( Transaction tx = graphDb.beginTx() ) + { + Node a = graphDb.createNode(), b = graphDb.createNode(), c = graphDb.createNode(), + d = graphDb.createNode(), e = graphDb.createNode(), f = graphDb.createNode(); + + a.createRelationshipTo( b, withName( "CIRCLE" ) ); + b.createRelationshipTo( c, withName( "CIRCLE" ) ); + one = c.createRelationshipTo( d, withName( "CIRCLE" ) ).getId(); + d.createRelationshipTo( e, withName( "CIRCLE" ) ); + e.createRelationshipTo( f, withName( "CIRCLE" ) ); + f.createRelationshipTo( a, withName( "CIRCLE" ) ); + + a.createRelationshipTo( b, withName( "TRIANGLE" ) ); + a.createRelationshipTo( c, withName( "TRIANGLE" ) ); + b.createRelationshipTo( c, withName( "TRIANGLE" ) ); + none = (deleted = c.createRelationshipTo( b, withName( "TRIANGLE" ) )).getId(); + RelationshipScanCursorTestBase.c = c.getId(); + RelationshipScanCursorTestBase.d = d.getId(); + + d.createRelationshipTo( e, withName( "TRIANGLE" ) ); + e.createRelationshipTo( f, withName( "TRIANGLE" ) ); + f.createRelationshipTo( d, withName( "TRIANGLE" ) ); + + loop = a.createRelationshipTo( a, withName( "LOOP" ) ).getId(); + + tx.success(); + } + + RELATIONSHIP_IDS = new ArrayList<>(); + try ( Transaction tx = graphDb.beginTx() ) + { + deleted.delete(); + for ( Relationship relationship : graphDb.getAllRelationships() ) + { + RELATIONSHIP_IDS.add( relationship.getId() ); + } + tx.success(); + } + } + + @Test + public void shouldScanRelationships() throws Exception + { + // given + List ids = new ArrayList<>(); + try ( RelationshipScanCursor relationships = cursors.allocateRelationshipScanCursor() ) + { + // when + read.allRelationshipsScan( relationships ); + while ( relationships.next() ) + { + ids.add( relationships.relationshipReference() ); + } + } + + assertEquals( RELATIONSHIP_IDS, ids ); + } + + @Test + public void shouldAccessRelationshipByReference() throws Exception + { + // given + try ( RelationshipScanCursor relationships = cursors.allocateRelationshipScanCursor() ) + { + for ( long id : RELATIONSHIP_IDS ) + { + // when + read.singleRelationship( id, relationships ); + + // then + assertTrue( "should access defined relationship", relationships.next() ); + assertEquals( "should access the correct relationship", id, relationships.relationshipReference() ); + assertFalse( "should only access a single relationship", relationships.next() ); + } + } + } + + @Test + public void shouldNotAccessDeletedRelationship() throws Exception + { + // given + try ( RelationshipScanCursor relationships = cursors.allocateRelationshipScanCursor() ) + { + // when + read.singleRelationship( none, relationships ); + + // then + assertFalse( "should not access deleted relationship", relationships.next() ); + } + } + + @Test + public void shouldAccessRelationshipLabels() throws Exception + { + // given + Map counts = new HashMap<>(); + + try ( RelationshipScanCursor relationships = cursors.allocateRelationshipScanCursor() ) + { + // when + read.allRelationshipsScan( relationships ); + while ( relationships.next() ) + { + counts.compute( relationships.label(), ( k, v ) -> v == null ? 1 : v + 1 ); + } + } + + // then + assertEquals( 3, counts.size() ); + int[] values = new int[3]; + int i = 0; + for ( int value : counts.values() ) + { + values[i++] = value; + } + Arrays.sort( values ); + assertArrayEquals( new int[]{1, 6, 6}, values ); + } + + @Test + public void shouldAccessNodes() throws Exception + { + // given + try ( RelationshipScanCursor relationships = cursors.allocateRelationshipScanCursor() ) + { + // when + read.singleRelationship( one, relationships ); + + // then + assertTrue( relationships.next() ); + assertEquals( c, relationships.sourceNodeReference() ); + assertEquals( d, relationships.targetNodeReference() ); + assertFalse( relationships.next() ); + + // when + read.singleRelationship( loop, relationships ); + + // then + assertTrue( relationships.next() ); + assertEquals( relationships.sourceNodeReference(), relationships.targetNodeReference() ); + assertFalse( relationships.next() ); + } + } +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTraversalCursorTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTraversalCursorTestBase.java new file mode 100644 index 0000000000000..80f3326e6081a --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTraversalCursorTestBase.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2002-2017 "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 . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.Test; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.Transaction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.neo4j.graphdb.RelationshipType.withName; + +public abstract class RelationshipTraversalCursorTestBase + extends KernelAPIReadTestBase +{ + private static long bare, start, end; + + @Override + void createTestGraph( GraphDatabaseService graphDb ) + { + Relationship dead; + try ( Transaction tx = graphDb.beginTx() ) + { + Node a = graphDb.createNode(), + b = graphDb.createNode(), + c = graphDb.createNode(), + d = graphDb.createNode(); + + a.createRelationshipTo( a, withName( "ALPHA" ) ); + a.createRelationshipTo( b, withName( "BETA" ) ); + a.createRelationshipTo( c, withName( "GAMMA" ) ); + a.createRelationshipTo( d, withName( "DELTA" ) ); + + bare = graphDb.createNode().getId(); + + Node x = graphDb.createNode(), y = graphDb.createNode(); + start = x.getId(); + end = y.getId(); + x.createRelationshipTo( y, withName( "GEN" ) ); + + graphDb.createNode().createRelationshipTo( a, withName( "BETA" ) ); + a.createRelationshipTo( graphDb.createNode(), withName( "BETA" ) ); + dead = a.createRelationshipTo( graphDb.createNode(), withName( "BETA" ) ); + a.createRelationshipTo( graphDb.createNode(), withName( "BETA" ) ); + + Node clump = graphDb.createNode(); + clump.createRelationshipTo( clump, withName( "REL" ) ); + clump.createRelationshipTo( clump, withName( "REL" ) ); + clump.createRelationshipTo( clump, withName( "REL" ) ); + clump.createRelationshipTo( graphDb.createNode(), withName( "REL" ) ); + clump.createRelationshipTo( graphDb.createNode(), withName( "REL" ) ); + clump.createRelationshipTo( graphDb.createNode(), withName( "REL" ) ); + graphDb.createNode().createRelationshipTo( clump, withName( "REL" ) ); + graphDb.createNode().createRelationshipTo( clump, withName( "REL" ) ); + graphDb.createNode().createRelationshipTo( clump, withName( "REL" ) ); + + tx.success(); + } + + try ( Transaction tx = graphDb.beginTx() ) + { + Node node = dead.getEndNode(); + dead.delete(); + node.delete(); + + tx.success(); + } + } + + @Test + public void shouldNotAccessGroupsOfBareNode() throws Exception + { + // given + try ( NodeCursor node = cursors.allocateNodeCursor(); + RelationshipGroupCursor group = cursors.allocateRelationshipGroupCursor() ) + { + // when + read.singleNode( bare, node ); + assertTrue( "access node", node.next() ); + node.relationships( group ); + + // then + assertFalse( "access group", group.next() ); + } + } + + @Test + public void shouldTraverseRelationshipsOfGivenType() throws Exception + { + // given + try ( NodeCursor node = cursors.allocateNodeCursor(); + RelationshipGroupCursor group = cursors.allocateRelationshipGroupCursor(); + RelationshipTraversalCursor relationship = cursors.allocateRelationshipTraversalCursor() ) + { + int empty = 0; + // when + read.allNodesScan( node ); + while ( node.next() ) + { + node.relationships( group ); + boolean none = true; + while ( group.next() ) + { + none = false; + Sizes degree = new Sizes(); + group.outgoing( relationship ); + while ( relationship.next() ) + { + assertEquals( "relationship should have same label as group", group.relationshipLabel(), + relationship.label() ); + degree.outgoing++; + } + group.incoming( relationship ); + while ( relationship.next() ) + { + assertEquals( "relationship should have same label as group", group.relationshipLabel(), + relationship.label() ); + degree.incoming++; + } + group.loops( relationship ); + while ( relationship.next() ) + { + assertEquals( "relationship should have same label as group", group.relationshipLabel(), + relationship.label() ); + degree.loop++; + } + + // then + assertNotEquals( "all", 0, degree.incoming + degree.outgoing + degree.loop ); + assertEquals( "outgoing", group.outgoingCount(), degree.outgoing ); + assertEquals( "incoming", group.incomingCount(), degree.incoming ); + assertEquals( "loop", group.loopCount(), degree.loop ); + assertEquals( "all = incoming + outgoing - loop", + group.totalCount(), degree.incoming + degree.outgoing - degree.loop ); + } + if ( none ) + { + empty++; + } + } + + // then + assertEquals( "number of empty nodes", 1, empty ); + } + } + + @Test + public void shouldFollowSpecificRelationship() throws Exception + { + // given + try ( NodeCursor node = cursors.allocateNodeCursor(); + RelationshipGroupCursor group = cursors.allocateRelationshipGroupCursor(); + RelationshipTraversalCursor relationship = cursors.allocateRelationshipTraversalCursor() ) + { + // when - traversing from start to end + read.singleNode( start, node ); + assertTrue( "access start node", node.next() ); + node.relationships( group ); + assertTrue( "access relationship group", group.next() ); + group.outgoing( relationship ); + assertTrue( "access outgoing relationships", relationship.next() ); + + // then + assertEquals( "source node", start, relationship.sourceNodeReference() ); + assertEquals( "target node", end, relationship.targetNodeReference() ); + + assertEquals( "node of origin", start, relationship.originNodeReference() ); + assertEquals( "neighbouring node", end, relationship.neighbourNodeReference() ); + + assertEquals( "relationship should have same label as group", group.relationshipLabel(), + relationship.label() ); + + assertFalse( "only a single relationship", relationship.next() ); + + group.incoming( relationship ); + assertFalse( "no incoming relationships", relationship.next() ); + group.loops( relationship ); + assertFalse( "no loop relationships", relationship.next() ); + + assertFalse( "only a single group", group.next() ); + + // when - traversing from end to start + read.singleNode( end, node ); + assertTrue( "access start node", node.next() ); + node.relationships( group ); + assertTrue( "access relationship group", group.next() ); + group.incoming( relationship ); + assertTrue( "access incoming relationships", relationship.next() ); + + // then + assertEquals( "source node", start, relationship.sourceNodeReference() ); + assertEquals( "target node", end, relationship.targetNodeReference() ); + + assertEquals( "node of origin", end, relationship.originNodeReference() ); + assertEquals( "neighbouring node", start, relationship.neighbourNodeReference() ); + + assertEquals( "relationship should have same label as group", group.relationshipLabel(), + relationship.label() ); + + assertFalse( "only a single relationship", relationship.next() ); + + group.outgoing( relationship ); + assertFalse( "no outgoing relationships", relationship.next() ); + group.loops( relationship ); + assertFalse( "no loop relationships", relationship.next() ); + + assertFalse( "only a single group", group.next() ); + } + } + + private class Sizes + { + int incoming, outgoing, loop; + } +} diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RuntimeITBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RuntimeITBase.java index 71802f4d8445e..b6e4486a9da0a 100644 --- a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RuntimeITBase.java +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RuntimeITBase.java @@ -31,37 +31,12 @@ public abstract class RuntimeITBase { - abstract Runtime runtime(); + public abstract Runtime runtime(); final int PROP = 1000; final int LABEL = 1000; final int IRRELEVANT_LABEL = -1000; - @Test - public void shouldDoNodeAllScan() - { - // given - Runtime r = runtime(); - long n1 = createNode( r ); - long n2 = createNode( r ); - long n3 = createNode( r ); - - // when - NodeCursor cursor = r.cursorFactory().allocateNodeCursor(); - r.read().allNodesScan( cursor ); - - List scannedSet = new ArrayList<>(); - while ( cursor.next() ) - { - scannedSet.add( cursor.nodeReference() ); - assertFalse( cursor.hasProperties() ); - assertThat( cursor.labels().numberOfLabels(), equalTo( 0 ) ); - } - - // then - assertThat( scannedSet, containsInAnyOrder( n1, n2, n3 ) ); - } - @Test public void shouldDoNodeLabelScan() { @@ -100,9 +75,9 @@ public void shouldDoAllRelationshipScan() long n2 = createNode( r ); long n3 = createNode( r ); - r.write().relationshipCreate( n1, LABEL, n2 ); - r.write().relationshipCreate( n2, LABEL, n3 ); - r.write().relationshipCreate( n3, LABEL, n1 ); + relate( n1, LABEL, n2 ); + relate( n2, LABEL, n3 ); + relate( n3, LABEL, n1 ); // when RelationshipScanCursor cursor = r.cursorFactory().allocateRelationshipScanCursor(); @@ -137,18 +112,18 @@ public void shouldExpandByRelationshipGroup() int label2 = 22; int label3 = 33; - r.write().relationshipCreate( n1, label1, n2 ); - r.write().relationshipCreate( n1, label1, n2 ); + relate( n1, label1, n2 ); + relate( n1, label1, n2 ); - r.write().relationshipCreate( n2, label1, n3 ); - r.write().relationshipCreate( n2, label2, n3 ); - r.write().relationshipCreate( n2, label2, n3 ); + relate( n2, label1, n3 ); + relate( n2, label2, n3 ); + relate( n2, label2, n3 ); - r.write().relationshipCreate( n3, label3, n1 ); - r.write().relationshipCreate( n3, label3, n1 ); - r.write().relationshipCreate( n3, label2, n1 ); - r.write().relationshipCreate( n3, label1, n1 ); - r.write().relationshipCreate( n3, label1, n1 ); + relate( n3, label3, n1 ); + relate( n3, label3, n1 ); + relate( n3, label2, n1 ); + relate( n3, label1, n1 ); + relate( n3, label1, n1 ); // when NodeCursor nodeCursor = r.cursorFactory().allocateNodeCursor(); @@ -220,6 +195,11 @@ else if ( dir == 1 ) ) ); } + private void relate( long n1, int label1, long n2 ) + { + runtime().write().relationshipCreate( n1, label1, n2 ); + } + private long createNode( Runtime r, int... labels ) { long nodeId = r.write().nodeCreate(); diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/TransactionStateTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/TransactionStateTestBase.java new file mode 100644 index 0000000000000..441992368bbd3 --- /dev/null +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/TransactionStateTestBase.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.internal.kernel.api; + +import org.junit.Test; + +import java.util.Arrays; + +import org.neo4j.helpers.collection.Iterables; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.neo4j.graphdb.Label.label; + +public abstract class TransactionStateTestBase extends KernelAPIWriteTestBase +{ + @Test + public void shouldSeeNodeInTransaction() throws Exception + { + long nodeId; + try ( Transaction tx = kernel.beginTransaction() ) + { + nodeId = tx.nodeCreate(); + try ( NodeCursor node = kernel.cursors().allocateNodeCursor() ) + { + tx.singleNode( nodeId, node ); + assertTrue( "should access node", node.next() ); + assertEquals( nodeId, node.nodeReference() ); + assertFalse( "should only find one node", node.next() ); + } + tx.success(); + } + + try ( org.neo4j.graphdb.Transaction tx = graphDb.beginTx() ) + { + assertEquals( nodeId, graphDb.getNodeById( nodeId ).getId() ); + } + } + + @Test + public void shouldSeeNewLabelledNodeInTransaction() throws Exception + { + long nodeId; + int labelId; + final String labelName = "Town"; + + try ( Transaction tx = kernel.beginTransaction() ) + { + nodeId = tx.nodeCreate(); + labelId = kernel.token().labelGetOrCreateForName( labelName ); + tx.nodeAddLabel( nodeId, labelId ); + + try ( NodeCursor node = kernel.cursors().allocateNodeCursor() ) + { + tx.singleNode( nodeId, node ); + assertTrue( "should access node", node.next() ); + + LabelSet labels = node.labels(); + assertEquals( 1, labels.numberOfLabels() ); + assertEquals( labelId, labels.label( 0 ) ); + assertFalse( "should only find one node", node.next() ); + } + tx.success(); + } + + try ( org.neo4j.graphdb.Transaction tx = graphDb.beginTx() ) + { + assertThat( + graphDb.getNodeById( nodeId ).getLabels(), + equalTo( Iterables.iterable( label( labelName ) ) ) ); + } + } + + @Test + public void shouldSeeLabelChangesInTransaction() throws Exception + { + long nodeId; + int toRetain, toDelete, toAdd; + final String toRetainName = "ToRetain"; + final String toDeleteName = "ToDelete"; + final String toAddName = "ToAdd"; + + try ( Transaction tx = kernel.beginTransaction() ) + { + nodeId = tx.nodeCreate(); + toRetain = kernel.token().labelGetOrCreateForName( toRetainName ); + toDelete = kernel.token().labelGetOrCreateForName( toDeleteName ); + tx.nodeAddLabel( nodeId, toRetain ); + tx.nodeAddLabel( nodeId, toDelete ); + tx.success(); + } + + try ( org.neo4j.graphdb.Transaction tx = graphDb.beginTx() ) + { + assertThat( + graphDb.getNodeById( nodeId ).getLabels(), + equalTo( Iterables.iterable( label( toRetainName ), label( toDeleteName ) ) ) ); + } + + try ( Transaction tx = kernel.beginTransaction() ) + { + toAdd = kernel.token().labelGetOrCreateForName( toAddName ); + tx.nodeAddLabel( nodeId, toAdd ); + tx.nodeRemoveLabel( nodeId, toDelete ); + + try ( NodeCursor node = kernel.cursors().allocateNodeCursor() ) + { + tx.singleNode( nodeId, node ); + assertTrue( "should access node", node.next() ); + + assertLabels( node.labels(), toRetain, toAdd ); + assertFalse( "should only find one node", node.next() ); + } + tx.success(); + } + + try ( org.neo4j.graphdb.Transaction tx = graphDb.beginTx() ) + { + assertThat( + graphDb.getNodeById( nodeId ).getLabels(), + equalTo( Iterables.iterable( label( toRetainName ), label( toAddName ) ) ) ); + } + } + + private void assertLabels( LabelSet labels, int... expected ) + { + assertEquals( expected.length, labels.numberOfLabels() ); + Arrays.sort(expected); + int[] labelArray = new int[labels.numberOfLabels()]; + for ( int i = 0; i < labels.numberOfLabels(); i++ ) + { + labelArray[i] = labels.label( i ); + } + Arrays.sort( labelArray ); + assertTrue( "labels match expected", Arrays.equals( expected, labelArray ) ); + } +} diff --git a/community/kernel/pom.xml b/community/kernel/pom.xml index 06700fbb2da18..baaea201755ed 100644 --- a/community/kernel/pom.xml +++ b/community/kernel/pom.xml @@ -256,6 +256,13 @@ the relevant Commercial Agreement. test-jar test + + org.neo4j + neo4j-kernel-api + ${project.version} + test-jar + test + org.neo4j neo4j-logging diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/PropertyUtil.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/PropertyUtil.java new file mode 100644 index 0000000000000..8d860905f34f5 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/PropertyUtil.java @@ -0,0 +1,67 @@ +package org.neo4j.kernel.impl.api.store; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.neo4j.kernel.impl.store.PropertyType; +import org.neo4j.kernel.impl.store.ShortArray; +import org.neo4j.kernel.impl.util.Bits; +import org.neo4j.string.UTF8; +import org.neo4j.values.storable.ArrayValue; +import org.neo4j.values.storable.Values; + +public class PropertyUtil +{ + public static ArrayValue readArrayFromBuffer( ByteBuffer buffer ) + { + if ( buffer.limit() <= 0 ) + { + throw new IllegalStateException( "Given buffer is empty" ); + } + + byte typeId = buffer.get(); + buffer.order( ByteOrder.BIG_ENDIAN ); + try + { + if ( typeId == PropertyType.STRING.intValue() ) + { + int arrayLength = buffer.getInt(); + String[] result = new String[arrayLength]; + + for ( int i = 0; i < arrayLength; i++ ) + { + int byteLength = buffer.getInt(); + result[i] = UTF8.decode( buffer.array(), buffer.position(), byteLength ); + buffer.position( buffer.position() + byteLength ); + } + return Values.stringArray( result ); + } + else + { + ShortArray type = ShortArray.typeOf( typeId ); + int bitsUsedInLastByte = buffer.get(); + int requiredBits = buffer.get(); + if ( requiredBits == 0 ) + { + return type.createEmptyArray(); + } + if ( type == ShortArray.BYTE && requiredBits == Byte.SIZE ) + { // Optimization for byte arrays (probably large ones) + byte[] byteArray = new byte[buffer.limit() - buffer.position()]; + buffer.get( byteArray ); + return Values.byteArray( byteArray ); + } + else + { // Fallback to the generic approach, which is a slower + Bits bits = Bits.bitsFromBytes( buffer.array(), buffer.position() ); + int length = ((buffer.limit() - buffer.position()) * 8 - (8 - bitsUsedInLastByte)) / requiredBits; + return type.createArray( length, bits, requiredBits ); + } + } + } + finally + { + buffer.order( ByteOrder.LITTLE_ENDIAN ); + } + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StorePropertyPayloadCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StorePropertyPayloadCursor.java index 4794994efe35f..f4aaac84851fc 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StorePropertyPayloadCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StorePropertyPayloadCursor.java @@ -31,7 +31,6 @@ import org.neo4j.kernel.impl.store.record.Record; import org.neo4j.kernel.impl.util.Bits; import org.neo4j.string.UTF8; -import org.neo4j.values.storable.ArrayValue; import org.neo4j.values.storable.BooleanValue; import org.neo4j.values.storable.ByteValue; import org.neo4j.values.storable.CharValue; @@ -224,7 +223,7 @@ Value arrayValue() assertOfType( ARRAY ); readFromStore( arrayRecordCursor ); buffer.flip(); - return readArrayFromBuffer( buffer ); + return PropertyUtil.readArrayFromBuffer( buffer ); } Value value() @@ -318,59 +317,6 @@ private ByteBuffer newBiggerBuffer( int requiredCapacity ) return ByteBuffer.allocate( newCapacity ).order( ByteOrder.LITTLE_ENDIAN ); } - private static ArrayValue readArrayFromBuffer( ByteBuffer buffer ) - { - if ( buffer.limit() <= 0 ) - { - throw new IllegalStateException( "Given buffer is empty" ); - } - - byte typeId = buffer.get(); - buffer.order( ByteOrder.BIG_ENDIAN ); - try - { - if ( typeId == PropertyType.STRING.intValue() ) - { - int arrayLength = buffer.getInt(); - String[] result = new String[arrayLength]; - - for ( int i = 0; i < arrayLength; i++ ) - { - int byteLength = buffer.getInt(); - result[i] = UTF8.decode( buffer.array(), buffer.position(), byteLength ); - buffer.position( buffer.position() + byteLength ); - } - return Values.stringArray( result ); - } - else - { - ShortArray type = ShortArray.typeOf( typeId ); - int bitsUsedInLastByte = buffer.get(); - int requiredBits = buffer.get(); - if ( requiredBits == 0 ) - { - return type.createEmptyArray(); - } - if ( type == ShortArray.BYTE && requiredBits == Byte.SIZE ) - { // Optimization for byte arrays (probably large ones) - byte[] byteArray = new byte[buffer.limit() - buffer.position()]; - buffer.get( byteArray ); - return Values.byteArray( byteArray ); - } - else - { // Fallback to the generic approach, which is a slower - Bits bits = Bits.bitsFromBytes( buffer.array(), buffer.position() ); - int length = ((buffer.limit() - buffer.position()) * 8 - (8 - bitsUsedInLastByte)) / requiredBits; - return type.createArray( length, bits, requiredBits ); - } - } - } - finally - { - buffer.order( ByteOrder.LITTLE_ENDIAN ); - } - } - private void assertOfType( PropertyType expected ) { assert type() == expected : "Expected type " + expected + " but was " + type(); diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/NodeCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/NodeCursor.java index 798f790da0c5f..7778a3335a1c6 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/NodeCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/NodeCursor.java @@ -28,6 +28,8 @@ import org.neo4j.kernel.impl.store.record.DynamicRecord; import org.neo4j.kernel.impl.store.record.NodeRecord; +import static org.neo4j.kernel.impl.newapi.Read.invertReference; + class NodeCursor extends NodeRecord implements org.neo4j.internal.kernel.api.NodeCursor { private final Read read; @@ -97,7 +99,7 @@ public long relationshipGroupReference() { // use negatives to encode the fact that this is not a proper group reference, // although not -1, because it is special - return isDense() ? getNextRel() : -2 - getNextRel(); + return isDense() ? getNextRel() : invertReference( getNextRel() ); } @Override @@ -114,22 +116,28 @@ public boolean next() close(); return false; } - read.node( this, next++ ); - if ( next > highMark ) + do { - if ( highMark == NO_ID ) - { - next = NO_ID; - } - else + read.node( this, next++ ); + if ( next > highMark ) { - highMark = read.nodeHighMark(); - if ( next > highMark ) + if ( highMark == NO_ID ) { next = NO_ID; + return inUse(); + } + else + { + highMark = read.nodeHighMark(); + if ( next > highMark ) + { + next = NO_ID; + return inUse(); + } } } } + while ( !inUse() ); return true; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/PropertyCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/PropertyCursor.java index 15d31e0e91560..a1c341966ebe9 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/PropertyCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/PropertyCursor.java @@ -19,18 +19,36 @@ */ package org.neo4j.kernel.impl.newapi; +import java.nio.ByteBuffer; import java.util.regex.Pattern; -import org.neo4j.kernel.impl.store.NeoStores; +import org.neo4j.kernel.impl.store.LongerShortString; +import org.neo4j.kernel.impl.store.PropertyType; +import org.neo4j.kernel.impl.store.ShortArray; +import org.neo4j.kernel.impl.store.record.PropertyBlock; import org.neo4j.kernel.impl.store.record.PropertyRecord; -import org.neo4j.kernel.impl.store.record.Record; +import org.neo4j.kernel.impl.util.Bits; +import org.neo4j.values.storable.ArrayValue; +import org.neo4j.values.storable.BooleanValue; +import org.neo4j.values.storable.ByteValue; +import org.neo4j.values.storable.DoubleValue; +import org.neo4j.values.storable.FloatValue; +import org.neo4j.values.storable.IntValue; +import org.neo4j.values.storable.LongValue; +import org.neo4j.values.storable.ShortValue; +import org.neo4j.values.storable.TextValue; import org.neo4j.values.storable.Value; import org.neo4j.values.storable.ValueGroup; import org.neo4j.values.storable.ValueWriter; +import org.neo4j.values.storable.Values; public class PropertyCursor extends PropertyRecord implements org.neo4j.internal.kernel.api.PropertyCursor { + private static final int MAX_BYTES_IN_SHORT_STRING_OR_SHORT_ARRAY = 32; private final Read read; + private long next; + private int block; + ByteBuffer buffer; public PropertyCursor( Read read ) { @@ -38,15 +56,47 @@ public PropertyCursor( Read read ) this.read = read; } - void init( NeoStores stores, long reference ) + void init( long reference ) { - throw new UnsupportedOperationException( "not implemented" ); + if ( getId() != NO_ID ) + { + close(); + } + next = reference; + block = Integer.MAX_VALUE; } @Override public boolean next() { - throw new UnsupportedOperationException( "not implemented" ); + if ( block < getNumberOfBlocks() ) + { + if ( block == -1 ) + { + block = 0; + } + else + { + block += type().calculateNumberOfBlocksUsed( currentBlock() ); + } + if ( block < getNumberOfBlocks() && type() != null ) + { + return true; + } + } + if ( next == NO_ID ) + { + return false; + } + read.property( this, next ); + next = getNextProp(); + block = -1; + return next(); + } + + private long currentBlock() + { + return getBlocks()[block]; } @Override @@ -58,25 +108,161 @@ public boolean shouldRetry() @Override public void close() { - throw new UnsupportedOperationException( "not implemented" ); + clear(); } @Override public int propertyKey() { - throw new UnsupportedOperationException( "not implemented" ); + return PropertyBlock.keyIndexId( currentBlock() ); } @Override public ValueGroup propertyType() { - throw new UnsupportedOperationException( "not implemented" ); + PropertyType type = type(); + if ( type == null ) + { + return ValueGroup.NO_VALUE; + } + switch ( type ) + { + case BOOL: + return ValueGroup.BOOLEAN; + case BYTE: + case SHORT: + case INT: + case LONG: + case FLOAT: + case DOUBLE: + return ValueGroup.NUMBER; + case STRING: + case CHAR: + case SHORT_STRING: + return ValueGroup.TEXT; + case SHORT_ARRAY: + case ARRAY: + default: + throw new UnsupportedOperationException( "not implemented" ); + } + } + + private PropertyType type() + { + return PropertyType.getPropertyTypeOrNull( currentBlock() ); } @Override public Value propertyValue() { - throw new UnsupportedOperationException( "not implemented" ); + PropertyType type = type(); + if ( type == null ) + { + return Values.NO_VALUE; + } + switch ( type ) + { + case BOOL: + return readBoolean(); + case BYTE: + return readByte(); + case SHORT: + return readShort(); + case INT: + return readInt(); + case LONG: + return readLong(); + case FLOAT: + return readFloat(); + case DOUBLE: + return readDouble(); + case CHAR: + return readChar(); + case SHORT_STRING: + return readShortString(); + case SHORT_ARRAY: + return readShortArray(); + case STRING: + return readLongString(); + case ARRAY: + return readLongArray(); + default: + throw new IllegalStateException( "Unsupported PropertyType: " + type.name() ); + } + } + + private ArrayValue readLongArray() + { + return read.array( this, PropertyBlock.fetchLong( currentBlock() ) ); + } + + private TextValue readLongString() + { + return read.string( this, PropertyBlock.fetchLong( currentBlock() ) ); + } + + private Value readShortArray() + { + Bits bits = Bits.bits( MAX_BYTES_IN_SHORT_STRING_OR_SHORT_ARRAY ); + int blocksUsed = ShortArray.calculateNumberOfBlocksUsed( currentBlock() ); + for ( int i = 0; i < blocksUsed; i++ ) + { + bits.put( getBlocks()[block + i] ); + } + return ShortArray.decode( bits ); + } + + private TextValue readShortString() + { + return LongerShortString + .decode( getBlocks(), block, LongerShortString.calculateNumberOfBlocksUsed( currentBlock() ) ); + } + + private TextValue readChar() + { + return Values.charValue( (char) PropertyBlock.fetchShort( currentBlock() ) ); + } + + private DoubleValue readDouble() + { + return Values.doubleValue( Double.longBitsToDouble( getBlocks()[block + 1] ) ); + } + + private FloatValue readFloat() + { + return Values.floatValue( Float.intBitsToFloat( PropertyBlock.fetchInt( currentBlock() ) ) ); + } + + private LongValue readLong() + { + if ( PropertyBlock.valueIsInlined( currentBlock() ) ) + { + return Values.longValue( PropertyBlock.fetchLong( currentBlock() ) >>> 1 ); + } + else + { + return Values.longValue( getBlocks()[block + 1] ); + } + } + + private IntValue readInt() + { + return Values.intValue( PropertyBlock.fetchInt( currentBlock() ) ); + } + + private ShortValue readShort() + { + return Values.shortValue( PropertyBlock.fetchShort( currentBlock() ) ); + } + + private ByteValue readByte() + { + return Values.byteValue( PropertyBlock.fetchByte( currentBlock() ) ); + } + + private BooleanValue readBoolean() + { + return Values.booleanValue( PropertyBlock.fetchByte( currentBlock() ) == 1 ); } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java index 9a2f1c4301f43..bcf87ef54fefc 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java @@ -19,17 +19,30 @@ */ package org.neo4j.kernel.impl.newapi; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + import org.neo4j.internal.kernel.api.IndexPredicate; import org.neo4j.internal.kernel.api.IndexReference; import org.neo4j.internal.kernel.api.Scan; +import org.neo4j.kernel.impl.api.store.PropertyUtil; +import org.neo4j.kernel.impl.store.AbstractDynamicStore; import org.neo4j.kernel.impl.store.NeoStores; import org.neo4j.kernel.impl.store.RecordCursor; import org.neo4j.kernel.impl.store.RecordStore; import org.neo4j.kernel.impl.store.record.AbstractBaseRecord; import org.neo4j.kernel.impl.store.record.DynamicRecord; import org.neo4j.kernel.impl.store.record.NodeRecord; +import org.neo4j.kernel.impl.store.record.PropertyRecord; +import org.neo4j.kernel.impl.store.record.RecordLoad; +import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; import org.neo4j.kernel.impl.store.record.RelationshipRecord; +import org.neo4j.string.UTF8; +import org.neo4j.values.storable.ArrayValue; +import org.neo4j.values.storable.TextValue; +import org.neo4j.values.storable.Values; +import static org.neo4j.kernel.impl.store.record.AbstractBaseRecord.NO_ID; import static org.neo4j.kernel.impl.store.record.RecordLoad.NORMAL; public class Read implements org.neo4j.internal.kernel.api.Read @@ -120,26 +133,73 @@ public Scan relationshipLa public void relationshipGroups( long nodeReference, long reference, org.neo4j.internal.kernel.api.RelationshipGroupCursor cursor ) { - ((RelationshipGroupCursor) cursor).init( stores, nodeReference, reference ); + if ( reference == NO_ID ) // there are no relationships for this node + { + cursor.close(); + } + else if ( reference < NO_ID ) // the relationships for this node are not grouped + { + ((RelationshipGroupCursor) cursor).buffer( nodeReference, invertReference( reference ) ); + } + else // this is a normal group reference. + { + ((RelationshipGroupCursor) cursor).direct( nodeReference, reference ); + } } @Override public void relationships( long nodeReference, long reference, org.neo4j.internal.kernel.api.RelationshipTraversalCursor cursor ) { - ((RelationshipTraversalCursor) cursor).init( nodeReference, reference ); + /* TODO: There are actually five (5!) different ways a relationship traversal cursor can be initialized: + * + * 1. From a batched group in a detached way. This happens when the user manually retrieves the relationships + * references from the group cursor and passes it to this method and if the group cursor was based on having + * batched all the different types in the single (mixed) chain of relationships. + * In this case we should pass a reference marked with some flag to the first relationship in the chain that + * has the type of the current group in the group cursor. The traversal cursor then needs to read the type + * from that first record and use that type as a filter for when reading the rest of the chain. + * - NOTE: we probably have to do the same sort of filtering for direction - so we need a flag for that too. + * + * 2. From a batched group in a DIRECT way. This happens when the traversal cursor is initialized directly from + * the group cursor, in this case we can simply initialize the traversal cursor with the buffered state from + * the group cursor, so this method here does not have to be involved, and things become pretty simple. + * + * 3. Traversing all relationships - regardless of type - of a node that has grouped relationships. In this case + * the traversal cursor needs to traverse through the group records in order to get to the actual + * relationships. The initialization of the cursor (through this here method) should be with a FLAGGED + * reference to the (first) group record. + * + * 4. Traversing a single chain - this is what happens in the cases when + * a) Traversing all relationships of a node without grouped relationships. + * b) Traversing the relationships of a particular group of a node with grouped relationships. + * + * 5. There are no relationships - i.e. passing in NO_ID to this method. + */ + if ( reference == NO_ID ) // there are no relationships for this node + { + cursor.close(); + } + else if ( reference < NO_ID ) // this reference is actually to a group record + { + ((RelationshipTraversalCursor) cursor).groups( nodeReference, invertReference( reference ) ); + } + else // this is a normal relationship reference + { + ((RelationshipTraversalCursor) cursor).chain( nodeReference, reference ); + } } @Override public void nodeProperties( long reference, org.neo4j.internal.kernel.api.PropertyCursor cursor ) { - ((PropertyCursor) cursor).init( stores, reference ); + ((PropertyCursor) cursor).init( reference ); } @Override public void relationshipProperties( long reference, org.neo4j.internal.kernel.api.PropertyCursor cursor ) { - ((PropertyCursor) cursor).init( stores, reference ); + ((PropertyCursor) cursor).init( reference ); } @Override @@ -174,21 +234,105 @@ private static RecordCursor newCursor( RecordS void node( NodeRecord record, long reference ) { - throw new UnsupportedOperationException( "not implemented" ); + stores.getNodeStore().getRecord( reference, record, RecordLoad.CHECK ); } void relationship( RelationshipRecord record, long reference ) + { + stores.getRelationshipStore().getRecord( reference, record, RecordLoad.CHECK ); + } + + void property( PropertyRecord record, long reference ) + { + stores.getPropertyStore().getRecord( reference, record, RecordLoad.NORMAL ); + } + + void group( RelationshipGroupRecord record, long reference ) { throw new UnsupportedOperationException( "not implemented" ); } long nodeHighMark() { - throw new UnsupportedOperationException( "not implemented" ); + return stores.getNodeStore().getHighestPossibleIdInUse(); } long relationshipHighMark() { - throw new UnsupportedOperationException( "not implemented" ); + return stores.getRelationshipStore().getHighestPossibleIdInUse(); + } + + TextValue string( PropertyCursor cursor, long reference ) + { + ByteBuffer buffer = + cursor.buffer = readDynamic( stores.getPropertyStore().getStringStore(), reference, cursor.buffer ); + buffer.flip(); + return Values.stringValue( UTF8.decode( buffer.array(), 0, buffer.limit() ) ); + } + + ArrayValue array( PropertyCursor cursor, long reference ) + { + ByteBuffer buffer = + cursor.buffer = readDynamic( stores.getPropertyStore().getArrayStore(), reference, cursor.buffer ); + buffer.flip(); + return PropertyUtil.readArrayFromBuffer( buffer ); + } + + /** + * Inverted references are used to signal that the reference is of a special type. + *

+ * For example that it is a direct reference to a relationship record rather than a reference to relationship group + * record in {@link NodeCursor#relationshipGroupReference()}. + *

+ * Since {@code -1} is used to encode {@link AbstractBaseRecord#NO_ID that the reference is invalid}, we reserve + * this value from the range by subtracting the reference from {@code -2} when inverting. + *

+ * This function is its own inverse function. + * + * @param reference + * the reference to invert. + * @return the inverted reference. + */ + static long invertReference( long reference ) + { + return -2 - reference; + } + + private static ByteBuffer readDynamic( AbstractDynamicStore store, long reference, ByteBuffer buffer ) + { + if ( buffer == null ) + { + buffer = ByteBuffer.allocate( 512 ); + } + else + { + buffer.clear(); + } + DynamicRecord record = store.newRecord(); + do + { + store.getRecord( reference, record, RecordLoad.NORMAL ); + reference = record.getNextBlock(); + byte[] data = record.getData(); + if ( buffer.remaining() < data.length ) + { + buffer = grow( buffer, data.length ); + } + buffer.put( data, 0, data.length ); + } + while ( reference != NO_ID ); + return buffer; + } + + private static ByteBuffer grow( ByteBuffer buffer, int required ) + { + buffer.flip(); + int capacity = buffer.capacity(); + do + { + capacity *= 2; + } + while ( capacity - buffer.limit() < required ); + return ByteBuffer.allocate( capacity ).order( ByteOrder.LITTLE_ENDIAN ).put( buffer ); } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipGroupCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipGroupCursor.java index 10e582362347f..3a021ef3ccc3d 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipGroupCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipGroupCursor.java @@ -19,14 +19,19 @@ */ package org.neo4j.kernel.impl.newapi; -import org.neo4j.internal.kernel.api.RelationshipTraversalCursor; -import org.neo4j.kernel.impl.store.NeoStores; +import org.neo4j.collection.primitive.Primitive; +import org.neo4j.collection.primitive.PrimitiveIntObjectMap; import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; +import org.neo4j.kernel.impl.store.record.RelationshipRecord; + +import static org.neo4j.kernel.impl.newapi.Read.invertReference; class RelationshipGroupCursor extends RelationshipGroupRecord implements org.neo4j.internal.kernel.api.RelationshipGroupCursor { private final Read read; + private final RelationshipRecord edge = new RelationshipRecord( NO_ID ); + private Group current; RelationshipGroupCursor( Read read ) { @@ -34,9 +39,57 @@ class RelationshipGroupCursor extends RelationshipGroupRecord this.read = read; } - void init( NeoStores stores, long nodeReference, long reference ) + void buffer( long nodeReference, long relationshipReference ) { - throw new UnsupportedOperationException( "not implemented" ); + setOwningNode( nodeReference ); + setId( NO_ID ); + setNext( NO_ID ); + // TODO: read first record to get the required capacity (from the count value in the prev field) + try ( PrimitiveIntObjectMap buffer = Primitive.intObjectMap() ) + { + Group current = null; + while ( relationshipReference != NO_ID ) + { + read.relationship( edge, relationshipReference ); + // find the group + Group group = buffer.get( edge.getType() ); + if ( group == null ) + { + buffer.put( edge.getType(), current = group = new Group( edge, current ) ); + } + // buffer the relationship into the group + if ( edge.getFirstNode() == nodeReference ) // outgoing or loop + { + if ( edge.getSecondNode() == nodeReference ) // loop + { + group.loop( edge ); + } + else // outgoing + { + group.outgoing( edge ); + } + relationshipReference = edge.getFirstNextRel(); + } + else if ( edge.getSecondNode() == nodeReference ) // incoming + { + group.incoming( edge ); + relationshipReference = edge.getSecondNextRel(); + } + else + { + throw new IllegalStateException( "not a part of the chain! TODO: better exception" ); + } + } + this.current = new Group( edge, current ); // we need a dummy before the first to denote the initial pos + } + } + + void direct( long nodeReference, long reference ) + { + setOwningNode( nodeReference ); + current = null; + clear(); + setNext( reference ); } @Override @@ -54,7 +107,25 @@ public void resume( Position position ) @Override public boolean next() { - throw new UnsupportedOperationException( "not implemented" ); + if ( current != null ) + { + current = current.next; + if ( current == null ) + { + return false; // we never have both types of traversal, so terminate early + } + setType( current.label ); + setFirstOut( current.outgoing() ); + setFirstIn( current.incoming() ); + setFirstLoop( current.loops() ); + return true; + } + if ( getNext() == NO_ID ) + { + return false; + } + read.group( this, getNext() ); + return true; } @Override @@ -66,66 +137,191 @@ public boolean shouldRetry() @Override public void close() { - throw new UnsupportedOperationException( "not implemented" ); + current = null; + setId( NO_ID ); + clear(); } @Override public int relationshipLabel() { - throw new UnsupportedOperationException( "not implemented" ); + return getType(); } @Override public int outgoingCount() { - throw new UnsupportedOperationException( "not implemented" ); + if ( current != null ) + { + return current.outgoingCount; + } + return count( outgoingReference() ); } @Override public int incomingCount() { - throw new UnsupportedOperationException( "not implemented" ); + if ( current != null ) + { + return current.incomingCount; + } + return count( incomingReference() ); } @Override public int loopCount() { - throw new UnsupportedOperationException( "not implemented" ); + if ( current != null ) + { + return current.loopsCount; + } + return count( loopsReference() ); + } + + private int count( long reference ) + { + if ( reference == NO_ID ) + { + return 0; + } + read.relationship( edge, reference ); + if ( edge.getFirstNode() == getOwningNode() ) + { + return (int) edge.getFirstPrevRel(); + } + else + { + return (int) edge.getSecondPrevRel(); + } } @Override - public void outgoing( RelationshipTraversalCursor cursor ) + public void outgoing( org.neo4j.internal.kernel.api.RelationshipTraversalCursor cursor ) { - throw new UnsupportedOperationException( "not implemented" ); + if ( current != null && cursor instanceof RelationshipTraversalCursor ) + { + ((RelationshipTraversalCursor) cursor).buffered( getOwningNode(), current.outgoing ); + } + else + { + read.relationships( getOwningNode(), outgoingReference(), cursor ); + } } @Override - public void incoming( RelationshipTraversalCursor cursor ) + public void incoming( org.neo4j.internal.kernel.api.RelationshipTraversalCursor cursor ) { - throw new UnsupportedOperationException( "not implemented" ); + if ( current != null && cursor instanceof RelationshipTraversalCursor ) + { + ((RelationshipTraversalCursor) cursor).buffered( getOwningNode(), current.incoming ); + } + else + { + read.relationships( getOwningNode(), incomingReference(), cursor ); + } } @Override - public void loops( RelationshipTraversalCursor cursor ) + public void loops( org.neo4j.internal.kernel.api.RelationshipTraversalCursor cursor ) { - throw new UnsupportedOperationException( "not implemented" ); + if ( current != null && cursor instanceof RelationshipTraversalCursor ) + { + ((RelationshipTraversalCursor) cursor).buffered( getOwningNode(), current.loops ); + } + else + { + read.relationships( getOwningNode(), loopsReference(), cursor ); + } } @Override public long outgoingReference() { - throw new UnsupportedOperationException( "not implemented" ); + return getFirstOut(); } @Override public long incomingReference() { - throw new UnsupportedOperationException( "not implemented" ); + return getFirstIn(); } @Override public long loopsReference() { - throw new UnsupportedOperationException( "not implemented" ); + return getFirstLoop(); + } + + static class Group + { + final int label; + final Group next; + RelationshipTraversalCursor.Record outgoing; + RelationshipTraversalCursor.Record incoming; + RelationshipTraversalCursor.Record loops; + private long firstOut = NO_ID; + private long firstIn = NO_ID; + private long firstLoop = NO_ID; + int outgoingCount; + int incomingCount; + int loopsCount; + + Group( RelationshipRecord edge, Group next ) + { + this.label = edge.getType(); + this.next = next; + } + + void outgoing( RelationshipRecord edge ) + { + if ( outgoing == null ) + { + firstOut = edge.getId(); + } + outgoing = new RelationshipTraversalCursor.Record( edge, outgoing ); + outgoingCount++; + } + + void incoming( RelationshipRecord edge ) + { + if ( incoming == null ) + { + firstIn = edge.getId(); + } + incoming = new RelationshipTraversalCursor.Record( edge, incoming ); + incomingCount++; + } + + void loop( RelationshipRecord edge ) + { + if ( loops == null ) + { + firstLoop = edge.getId(); + } + loops = new RelationshipTraversalCursor.Record( edge, loops ); + loopsCount++; + } + + long outgoing() + { + return flaggedAsRequiringFiltering( firstOut ); + } + + long incoming() + { + return flaggedAsRequiringFiltering( firstIn ); + } + + long loops() + { + return flaggedAsRequiringFiltering( firstLoop ); + } + + private long flaggedAsRequiringFiltering( long reference ) + { + // set a high order bit as flag noting that "filtering is required" + // invert the reference to note that "this reference is special" + return invertReference( reference | 0x2000_0000_0000_0000L ); + } } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipScanCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipScanCursor.java index b9b26e44a3726..a522c286b31f1 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipScanCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipScanCursor.java @@ -62,22 +62,28 @@ public boolean next() close(); return false; } - read.relationship( this, next++ ); - if ( next > highMark ) + do { - if ( highMark == NO_ID ) + read.relationship( this, next++ ); + if ( next > highMark ) { - next = NO_ID; - } - else - { - highMark = read.relationshipHighMark(); - if ( next > highMark ) + if ( highMark == NO_ID ) { next = NO_ID; + return (label == -1 || label() == label) && inUse(); + } + else + { + highMark = read.relationshipHighMark(); + if ( next > highMark ) + { + next = NO_ID; + return (label == -1 || label() == label) && inUse(); + } } } } + while ( !inUse() ); } while ( label != -1 && label() != label ); return true; diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursor.java index ccd117cd9f423..1aa4bdba23d4e 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursor.java @@ -20,6 +20,7 @@ package org.neo4j.kernel.impl.newapi; import org.neo4j.internal.kernel.api.NodeCursor; +import org.neo4j.kernel.impl.store.record.RelationshipRecord; class RelationshipTraversalCursor extends RelationshipCursor implements org.neo4j.internal.kernel.api.RelationshipTraversalCursor @@ -32,13 +33,23 @@ class RelationshipTraversalCursor extends RelationshipCursor super( read ); } - void init( long nodeReference, long reference ) + void buffered( long nodeReference, Record record ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + void chain( long nodeReference, long reference ) { setId( NO_ID ); originNodeReference = nodeReference; next = reference; } + void groups( long nodeReference, long reference ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + @Override public Position suspend() { @@ -117,4 +128,14 @@ public void close() { setId( next = NO_ID ); } + + static class Record + { + final Record next; + + Record( RelationshipRecord record, Record next ) + { + this.next = next; + } + } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/TempKernel.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/TempKernel.java new file mode 100644 index 0000000000000..bd6f1add6f390 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/TempKernel.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.newapi; + +import org.neo4j.internal.kernel.api.CursorFactory; +import org.neo4j.internal.kernel.api.KernelAPI; +import org.neo4j.internal.kernel.api.Token; +import org.neo4j.kernel.impl.storageengine.impl.recordstorage.RecordStorageEngine; +import org.neo4j.kernel.impl.store.NeoStores; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.values.storable.Value; + +class TempKernel implements KernelAPI +{ + private final Transaction tx; + private final Cursors cursors; + + TempKernel( GraphDatabaseAPI db ) + { + RecordStorageEngine engine = db.getDependencyResolver().resolveDependency( RecordStorageEngine.class ); + this.tx = new Transaction( engine.testAccessNeoStores() ); + this.cursors = new Cursors( tx ); + } + + @Override + public Transaction beginTransaction() + { + return tx; + } + + @Override + public CursorFactory cursors() + { + return cursors; + } + + @Override + public Token token() + { + throw new UnsupportedOperationException( "not implemented" ); + } + + private static class Transaction extends Read implements org.neo4j.internal.kernel.api.Transaction + { + Transaction( NeoStores stores ) + { + super( stores ); + } + + @Override + public void success() + { + } + + @Override + public void failure() + { + } + + @Override + public void close() throws Exception + { + } + + @Override + public long nodeCreate() + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void nodeDelete( long node ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public long relationshipCreate( long sourceNode, int relationshipLabel, long targetNode ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void relationshipDelete( long relationship ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void nodeAddLabel( long node, int nodeLabel ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void nodeRemoveLabel( long node, int nodeLabel ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void nodeSetProperty( long node, int propertyKey, Object value ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void nodeRemoveProperty( long node, int propertyKey ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void relationshipSetProperty( long relationship, int propertyKey, Value value ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void relationshipRemoveProperty( long node, int propertyKey ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/CommonAbstractStore.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/CommonAbstractStore.java index 0a3718a704480..515b24972378b 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/CommonAbstractStore.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/CommonAbstractStore.java @@ -1038,7 +1038,7 @@ public RECORD getRecord( long id, RECORD record, RecordLoad mode ) } } - void readIntoRecord( long id, RECORD record, RecordLoad mode, PageCursor cursor ) throws IOException + public void readIntoRecord( long id, RECORD record, RecordLoad mode, PageCursor cursor ) throws IOException { // Mark the record with this id regardless of whether or not we load the contents of it. // This is done in this method since there are multiple call sites and they all want the id diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/CursorFactoryTestBase.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/NodeCursorTest.java similarity index 74% rename from community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/CursorFactoryTestBase.java rename to community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/NodeCursorTest.java index 951c50edbba88..371f9e59a85ba 100644 --- a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/CursorFactoryTestBase.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/NodeCursorTest.java @@ -17,8 +17,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.internal.kernel.api; +package org.neo4j.kernel.impl.newapi; -public class CursorFactoryTestBase +import org.neo4j.internal.kernel.api.NodeCursorTestBase; + +public class NodeCursorTest extends NodeCursorTestBase { + @Override + public ReadTestSupport newTestSupport() + { + return new ReadTestSupport(); + } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/PropertyCursorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/PropertyCursorTest.java new file mode 100644 index 0000000000000..04c9b40284e21 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/PropertyCursorTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.newapi; + +import org.neo4j.internal.kernel.api.PropertyCursorTestBase; + +public class PropertyCursorTest extends PropertyCursorTestBase +{ + @Override + public ReadTestSupport newTestSupport() + { + return new ReadTestSupport(); + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/ReadTestSupport.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/ReadTestSupport.java new file mode 100644 index 0000000000000..acc2d5bc94d78 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/ReadTestSupport.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.newapi; + +import java.io.File; +import java.io.IOException; +import java.util.function.Consumer; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.internal.kernel.api.KernelAPI; +import org.neo4j.internal.kernel.api.KernelAPIReadTestSupport; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.test.TestGraphDatabaseFactory; + +class ReadTestSupport implements KernelAPIReadTestSupport +{ + private GraphDatabaseService db; + + @Override + public void setup( File storeDir, Consumer create ) throws IOException + { + db = new TestGraphDatabaseFactory().newImpermanentDatabaseBuilder( storeDir ).newGraphDatabase(); + create.accept( db ); + } + + @Override + public void beforeEachTest() + { + } + + @Override + public KernelAPI kernelToTest() + { + return new TempKernel( (GraphDatabaseAPI) db ); + } + + @Override + public void tearDown() + { + db.shutdown(); + db = null; + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/RelationshipScanCursorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/RelationshipScanCursorTest.java new file mode 100644 index 0000000000000..aad4f3f06fecf --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/RelationshipScanCursorTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.newapi; + +import org.neo4j.internal.kernel.api.RelationshipScanCursorTestBase; + +public class RelationshipScanCursorTest extends RelationshipScanCursorTestBase +{ + @Override + public ReadTestSupport newTestSupport() + { + return new ReadTestSupport(); + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursorTest.java new file mode 100644 index 0000000000000..e240d48f4c56d --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursorTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2002-2017 "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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.newapi; + +import org.neo4j.internal.kernel.api.RelationshipTraversalCursorTestBase; + +public class RelationshipTraversalCursorTest extends RelationshipTraversalCursorTestBase +{ + @Override + public ReadTestSupport newTestSupport() + { + return new ReadTestSupport(); + } +} diff --git a/community/values/src/main/java/org/neo4j/values/storable/BooleanValue.java b/community/values/src/main/java/org/neo4j/values/storable/BooleanValue.java index 6e55c40bc0469..b017d3bfe4a12 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/BooleanValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/BooleanValue.java @@ -27,9 +27,11 @@ */ public final class BooleanValue extends ScalarValue { + static final BooleanValue TRUE = new BooleanValue( true ); + static final BooleanValue FALSE = new BooleanValue( false ); private final boolean value; - BooleanValue( boolean value ) + private BooleanValue( boolean value ) { this.value = value; } diff --git a/community/values/src/main/java/org/neo4j/values/storable/Values.java b/community/values/src/main/java/org/neo4j/values/storable/Values.java index d510138964a12..d7efae172ae81 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/Values.java +++ b/community/values/src/main/java/org/neo4j/values/storable/Values.java @@ -190,7 +190,7 @@ public static ByteValue byteValue( byte value ) public static BooleanValue booleanValue( boolean value ) { - return new BooleanValue( value ); + return value ? BooleanValue.TRUE : BooleanValue.FALSE; } public static CharValue charValue( char value )