diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/cursor/NodeItemHelper.java b/community/kernel/src/main/java/org/neo4j/kernel/api/cursor/NodeItemHelper.java deleted file mode 100644 index a9ec8110b62f5..0000000000000 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/cursor/NodeItemHelper.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.api.cursor; - -import java.util.Arrays; - -import org.neo4j.collection.primitive.PrimitiveIntIterator; -import org.neo4j.cursor.Cursor; -import org.neo4j.kernel.impl.api.store.CursorRelationshipIterator; -import org.neo4j.kernel.impl.api.store.RelationshipIterator; -import org.neo4j.kernel.impl.util.Cursors; -import org.neo4j.storageengine.api.Direction; -import org.neo4j.storageengine.api.LabelItem; -import org.neo4j.storageengine.api.NodeItem; - -public abstract class NodeItemHelper - extends EntityItemHelper - implements NodeItem -{ - @Override - public boolean hasLabel( int labelId ) - { - try ( Cursor labelCursor = label( labelId ) ) - { - return labelCursor.next(); - } - } - - @Override - public PrimitiveIntIterator getLabels() - { - return Cursors.intIterator( labels(), GET_LABEL ); - } - - @Override - public RelationshipIterator getRelationships( Direction direction, int[] relTypes ) - { - relTypes = deduplicate( relTypes ); - - return new CursorRelationshipIterator( relationships( direction, relTypes ) ); - } - - @Override - public RelationshipIterator getRelationships( Direction direction ) - { - return new CursorRelationshipIterator( relationships( direction ) ); - } - - @Override - public PrimitiveIntIterator getRelationshipTypes() - { - return Cursors.intIterator( relationshipTypes(), GET_RELATIONSHIP_TYPE ); - } - - private static int[] deduplicate( int[] types ) - { - int unique = 0; - for ( int i = 0; i < types.length; i++ ) - { - int type = types[i]; - for ( int j = 0; j < unique; j++ ) - { - if ( type == types[j] ) - { - type = -1; // signal that this relationship is not unique - break; // we will not find more than one conflict - } - } - if ( type != -1 ) - { // this has to be done outside the inner loop, otherwise we'd never accept a single one... - types[unique++] = types[i]; - } - } - if ( unique < types.length ) - { - types = Arrays.copyOf( types, unique ); - } - return types; - } -} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/txstate/TransactionCountingStateVisitor.java b/community/kernel/src/main/java/org/neo4j/kernel/api/txstate/TransactionCountingStateVisitor.java index b1009ae6a77c6..38e987b577d4b 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/txstate/TransactionCountingStateVisitor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/txstate/TransactionCountingStateVisitor.java @@ -20,15 +20,17 @@ package org.neo4j.kernel.api.txstate; import java.util.Set; +import java.util.function.Consumer; -import org.neo4j.collection.primitive.PrimitiveIntCollections; -import org.neo4j.collection.primitive.PrimitiveIntIterator; +import org.neo4j.collection.primitive.Primitive; +import org.neo4j.collection.primitive.PrimitiveIntCollection; +import org.neo4j.collection.primitive.PrimitiveIntSet; import org.neo4j.cursor.Cursor; import org.neo4j.kernel.api.exceptions.EntityNotFoundException; import org.neo4j.kernel.api.exceptions.schema.ConstraintValidationKernelException; import org.neo4j.kernel.impl.api.CountsRecordState; import org.neo4j.kernel.impl.api.RelationshipDataExtractor; -import org.neo4j.storageengine.api.DegreeItem; +import org.neo4j.storageengine.api.LabelItem; import org.neo4j.storageengine.api.NodeItem; import org.neo4j.storageengine.api.StorageStatement; import org.neo4j.storageengine.api.StoreReadLayer; @@ -46,8 +48,7 @@ public class TransactionCountingStateVisitor extends TxStateVisitor.Delegator private final CountsRecordState counts; private final ReadableTransactionState txState; - public TransactionCountingStateVisitor( TxStateVisitor next, - StoreReadLayer storeLayer, StorageStatement statement, + public TransactionCountingStateVisitor( TxStateVisitor next, StoreReadLayer storeLayer, StorageStatement statement, ReadableTransactionState txState, CountsRecordState counts ) { super( next ); @@ -68,38 +69,24 @@ public void visitCreatedNode( long id ) public void visitDeletedNode( long id ) { counts.incrementNodeCount( ANY_LABEL, -1 ); - try ( Cursor node = statement.acquireSingleNodeCursor( id ) ) - { - if ( node.next() ) - { - // TODO Rewrite this to use cursors directly instead of iterator - PrimitiveIntIterator labels = node.get().getLabels(); - if ( labels.hasNext() ) - { - final int[] removed = PrimitiveIntCollections.asArray( labels ); - for ( int label : removed ) - { - counts.incrementNodeCount( label, -1 ); - } - - try ( Cursor degrees = node.get().degrees() ) - { - while ( degrees.next() ) - { - DegreeItem degree = degrees.get(); - for ( int label : removed ) - { - updateRelationshipsCountsFromDegrees( degree.type(), label, -degree.outgoing(), - -degree.incoming() ); - } - } - } - } - } - } + statement.acquireSingleNodeCursor( id ).forAll( this::decrementCountForLabelsAndRelationships ); super.visitDeletedNode( id ); } + private void decrementCountForLabelsAndRelationships( NodeItem node ) + { + PrimitiveIntSet labelIds = node.labels().collect( Primitive.intSet(), ( label ) -> + { + int labelId = label.getAsInt(); + counts.incrementNodeCount( labelId, -1 ); + return labelId; + } ); + + node.degrees().forAll( + degree -> updateRelationshipsCountsFromDegrees( labelIds, degree.type(), -degree.outgoing(), + -degree.incoming() ) ); + } + @Override public void visitCreatedRelationship( long id, int type, long startNode, long endNode ) throws ConstraintValidationKernelException @@ -118,8 +105,7 @@ public void visitDeletedRelationship( long id ) } catch ( EntityNotFoundException e ) { - throw new IllegalStateException( - "Relationship being deleted should exist along with its nodes.", e ); + throw new IllegalStateException( "Relationship being deleted should exist along with its nodes.", e ); } super.visitDeletedRelationship( id ); } @@ -141,35 +127,28 @@ public void visitNodeLabelChanges( long id, final Set added, final Set< } // get the relationship counts from *before* this transaction, // the relationship changes will compensate for what happens during the transaction - try ( Cursor node = statement.acquireSingleNodeCursor( id ) ) + statement.acquireSingleNodeCursor( id ).forAll( node -> node.degrees().forAll( degree -> { - if ( node.next() ) + for ( Integer label : added ) { - try ( Cursor degrees = node.get().degrees() ) - { - while ( degrees.next() ) - { - DegreeItem degree = degrees.get(); - - for ( Integer label : added ) - { - updateRelationshipsCountsFromDegrees( degree.type(), label, degree.outgoing(), - degree.incoming() ); - } - for ( Integer label : removed ) - { - updateRelationshipsCountsFromDegrees( degree.type(), label, -degree.outgoing(), - -degree.incoming() ); - } - } - } + updateRelationshipsCountsFromDegrees( degree.type(), label, degree.outgoing(), degree.incoming() ); } - } + for ( Integer label : removed ) + { + updateRelationshipsCountsFromDegrees( degree.type(), label, -degree.outgoing(), + -degree.incoming() ); + } + } ) ); } super.visitNodeLabelChanges( id, added, removed ); } - private void updateRelationshipsCountsFromDegrees( int type, int label, long outgoing, long incoming ) + private void updateRelationshipsCountsFromDegrees( PrimitiveIntCollection labels, int type, long outgoing, + long incoming ) + { + labels.visitKeys( (label) -> updateRelationshipsCountsFromDegrees( type, label, outgoing, incoming ) ); + } + private boolean updateRelationshipsCountsFromDegrees( int type, int label, long outgoing, long incoming ) { // untyped counts.incrementRelationshipCount( label, ANY_RELATIONSHIP_TYPE, ANY_LABEL, outgoing ); @@ -177,36 +156,23 @@ private void updateRelationshipsCountsFromDegrees( int type, int label, long out // typed counts.incrementRelationshipCount( label, type, ANY_LABEL, outgoing ); counts.incrementRelationshipCount( ANY_LABEL, type, label, incoming ); + return true; } private void updateRelationshipCount( long startNode, int type, long endNode, int delta ) { updateRelationshipsCountsFromDegrees( type, ANY_LABEL, delta, 0 ); - for ( PrimitiveIntIterator startLabels = labelsOf( startNode ); startLabels.hasNext(); ) - { - updateRelationshipsCountsFromDegrees( type, startLabels.next(), delta, 0 ); - } - for ( PrimitiveIntIterator endLabels = labelsOf( endNode ); endLabels.hasNext(); ) - { - updateRelationshipsCountsFromDegrees( type, endLabels.next(), 0, delta ); - } + visitLabels( startNode, ( label ) -> updateRelationshipsCountsFromDegrees( type, label.getAsInt(), delta, 0 ) ); + visitLabels( endNode, ( label ) -> updateRelationshipsCountsFromDegrees( type, label.getAsInt(), 0, delta ) ); } - private PrimitiveIntIterator labelsOf( long nodeId ) + private void visitLabels( long nodeId, Consumer consumer ) { - try ( Cursor node = nodeCursor( statement, nodeId ) ) - { - if ( node.next() ) - { - return node.get().getLabels(); - } - return PrimitiveIntCollections.emptyIterator(); - } + nodeCursor( statement, nodeId ).forAll( node -> node.labels().forAll( consumer ) ); } private Cursor nodeCursor( StorageStatement statement, long nodeId ) { - Cursor cursor = statement.acquireSingleNodeCursor( nodeId ); - return txState.augmentSingleNodeCursor( cursor, nodeId ); + return txState.augmentSingleNodeCursor( statement.acquireSingleNodeCursor( nodeId ), nodeId ); } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/OperationsFacade.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/OperationsFacade.java index 6a6bcc2cad065..615bad6300a11 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/OperationsFacade.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/OperationsFacade.java @@ -19,11 +19,13 @@ */ package org.neo4j.kernel.impl.api; +import java.util.Arrays; import java.util.Iterator; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.function.IntSupplier; import java.util.stream.Stream; import org.neo4j.collection.RawIterator; @@ -95,12 +97,16 @@ import org.neo4j.kernel.impl.api.operations.SchemaStateOperations; import org.neo4j.kernel.impl.api.security.OverriddenAccessMode; import org.neo4j.kernel.impl.api.security.RestrictedAccessMode; +import org.neo4j.kernel.impl.api.store.CursorRelationshipIterator; import org.neo4j.kernel.impl.api.store.RelationshipIterator; import org.neo4j.kernel.impl.proc.Procedures; import org.neo4j.kernel.impl.query.QuerySource; +import org.neo4j.kernel.impl.util.Cursors; import org.neo4j.register.Register.DoubleLongRegister; +import org.neo4j.storageengine.api.LabelItem; import org.neo4j.storageengine.api.NodeItem; import org.neo4j.storageengine.api.RelationshipItem; +import org.neo4j.storageengine.api.RelationshipTypeItem; import org.neo4j.storageengine.api.Token; import org.neo4j.storageengine.api.lock.ResourceType; import org.neo4j.storageengine.api.schema.PopulationProgress; @@ -319,7 +325,7 @@ public PrimitiveIntIterator nodeGetLabels( long nodeId ) throws EntityNotFoundEx statement.assertOpen(); try ( Cursor node = dataRead().nodeCursorById( statement, nodeId ) ) { - return node.get().getLabels(); + return Cursors.intIterator( node.get().labels(), LabelItem::getAsInt ); } } @@ -362,7 +368,8 @@ public RelationshipIterator nodeGetRelationships( long nodeId, Direction directi statement.assertOpen(); try ( Cursor node = dataRead().nodeCursorById( statement, nodeId ) ) { - return node.get().getRelationships( direction( direction ), relTypes ); + return new CursorRelationshipIterator( + node.get().relationships( direction( direction ), deduplicate( relTypes ) ) ); } } @@ -377,15 +384,40 @@ private org.neo4j.storageengine.api.Direction direction( Direction direction ) } } + private static int[] deduplicate( int[] types ) + { + int unique = 0; + for ( int i = 0; i < types.length; i++ ) + { + int type = types[i]; + for ( int j = 0; j < unique; j++ ) + { + if ( type == types[j] ) + { + type = -1; // signal that this relationship is not unique + break; // we will not find more than one conflict + } + } + if ( type != -1 ) + { // this has to be done outside the inner loop, otherwise we'd never accept a single one... + types[unique++] = types[i]; + } + } + if ( unique < types.length ) + { + types = Arrays.copyOf( types, unique ); + } + return types; + } + @Override public RelationshipIterator nodeGetRelationships( long nodeId, Direction direction ) throws EntityNotFoundException { statement.assertOpen(); - try ( Cursor node = dataRead().nodeCursorById( statement, nodeId ) ) { - return node.get().getRelationships( direction( direction ) ); + return new CursorRelationshipIterator( node.get().relationships( direction( direction ) ) ); } } @@ -425,7 +457,7 @@ public PrimitiveIntIterator nodeGetRelationshipTypes( long nodeId ) throws Entit statement.assertOpen(); try ( Cursor node = dataRead().nodeCursorById( statement, nodeId ) ) { - return node.get().getRelationshipTypes(); + return Cursors.intIterator( node.get().relationshipTypes(), RelationshipTypeItem::getAsInt ); } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/TwoPhaseNodeForRelationshipLocking.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/TwoPhaseNodeForRelationshipLocking.java index 3c4f72261c6a0..cb408f7989d39 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/TwoPhaseNodeForRelationshipLocking.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/TwoPhaseNodeForRelationshipLocking.java @@ -26,11 +26,10 @@ import org.neo4j.function.ThrowingConsumer; import org.neo4j.kernel.api.exceptions.KernelException; import org.neo4j.kernel.impl.api.operations.EntityReadOperations; -import org.neo4j.kernel.impl.api.store.RelationshipIterator; -import org.neo4j.kernel.impl.locking.Locks; import org.neo4j.kernel.impl.locking.ResourceTypes; import org.neo4j.storageengine.api.Direction; import org.neo4j.storageengine.api.NodeItem; +import org.neo4j.storageengine.api.RelationshipItem; class TwoPhaseNodeForRelationshipLocking { @@ -38,43 +37,10 @@ class TwoPhaseNodeForRelationshipLocking private final EntityReadOperations entityReadOperations; private final ThrowingConsumer relIdAction; - private boolean retry = true; private long firstRelId; - private boolean first; - private final RelationshipVisitor collectNodeIdVisitor = - (relId, type, startNode, endNode) -> { - if ( firstRelId == -1 ) - { - firstRelId = relId; - } - nodeIds.add( startNode ); - nodeIds.add( endNode ); - }; - - private final RelationshipVisitor relationshipConsumingVisitor = - new RelationshipVisitor() - { - @Override - public void visit( long relId, int type, long startNode, long endNode ) throws KernelException - { - if ( first ) - { - first = false; - if ( relId != firstRelId ) - { - // if the first relationship is not the same someone added some new rels, so we need to - // lock them all again - retry = true; - return; - } - } - - relIdAction.accept( relId ); - } - }; - - TwoPhaseNodeForRelationshipLocking( EntityReadOperations entityReadOperations, ThrowingConsumer relIdAction ) + TwoPhaseNodeForRelationshipLocking( EntityReadOperations entityReadOperations, + ThrowingConsumer relIdAction ) { this.entityReadOperations = entityReadOperations; this.relIdAction = relIdAction; @@ -82,48 +48,83 @@ public void visit( long relId, int type, long startNode, long endNode ) throws K void lockAllNodesAndConsumeRelationships( long nodeId, final KernelStatement state ) throws KernelException { - nodeIds.add( nodeId ); - while ( retry ) + boolean retry; + do { + nodeIds.add( nodeId ); retry = false; - first = true; firstRelId = -1; // lock all the nodes involved by following the node id ordering - try ( Cursor cursor = entityReadOperations.nodeCursorById( state, nodeId ) ) + try ( Cursor node = entityReadOperations.nodeCursorById( state, nodeId ) ) { - RelationshipIterator relationships = cursor.get().getRelationships( Direction.BOTH ); - while ( relationships.hasNext() ) - { - entityReadOperations.relationshipVisit( state, relationships.next(), collectNodeIdVisitor ); - } + node.get().relationships( Direction.BOTH ).forAll( this::collectNodeId ); } - PrimitiveLongIterator nodeIdIterator = nodeIds.iterator(); - while ( nodeIdIterator.hasNext() ) - { - state.locks().optimistic().acquireExclusive( state.lockTracer(), ResourceTypes.NODE, nodeIdIterator.next() ); - } + lockAllNodes( state ); // perform the action on each relationship, we will retry if the the relationship iterator contains new relationships - try ( Cursor cursor = entityReadOperations.nodeCursorById( state, nodeId ) ) + try ( Cursor node = entityReadOperations.nodeCursorById( state, nodeId ) ) { - RelationshipIterator relationships = cursor.get().getRelationships( Direction.BOTH ); - while ( relationships.hasNext() ) + try ( Cursor relationships = node.get().relationships( Direction.BOTH ) ) { - entityReadOperations.relationshipVisit( state, relationships.next(), relationshipConsumingVisitor ); - if ( retry ) + boolean first = true; + while ( relationships.next() && !retry ) { - PrimitiveLongIterator iterator = nodeIds.iterator(); - while ( iterator.hasNext() ) - { - state.locks().optimistic().releaseExclusive( ResourceTypes.NODE, iterator.next() ); - } - nodeIds.clear(); - break; + retry = performAction( state, relationships.get(), first ); + first = false; } } } } + while ( retry ); + } + + private void lockAllNodes( KernelStatement state ) + { + PrimitiveLongIterator nodeIdIterator = nodeIds.iterator(); + while ( nodeIdIterator.hasNext() ) + { + state.locks().optimistic() + .acquireExclusive( state.lockTracer(), ResourceTypes.NODE, nodeIdIterator.next() ); + } + } + + private void unlockAllNodes( KernelStatement state ) + { + PrimitiveLongIterator iterator = nodeIds.iterator(); + while ( iterator.hasNext() ) + { + state.locks().optimistic().releaseExclusive( ResourceTypes.NODE, iterator.next() ); + } + nodeIds.clear(); + } + + private boolean performAction( KernelStatement state, RelationshipItem rel, boolean first ) throws KernelException + { + if ( first ) + { + if ( rel.id() != firstRelId ) + { + // if the first relationship is not the same someone added some new rels, so we need to + // lock them all again + unlockAllNodes( state ); + return true; + } + } + + relIdAction.accept( rel.id() ); + return false; + } + + private void collectNodeId( RelationshipItem rel ) + { + if ( firstRelId == -1 ) + { + firstRelId = rel.id(); + } + + nodeIds.add( rel.startNode() ); + nodeIds.add( rel.endNode() ); } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/cursor/TxAbstractNodeCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/cursor/TxAbstractNodeCursor.java deleted file mode 100644 index 9140c7f7abc83..0000000000000 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/cursor/TxAbstractNodeCursor.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 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.api.cursor; - -import java.util.function.Consumer; -import java.util.function.IntSupplier; - -import org.neo4j.collection.primitive.Primitive; -import org.neo4j.collection.primitive.PrimitiveIntIterator; -import org.neo4j.collection.primitive.PrimitiveIntSet; -import org.neo4j.cursor.Cursor; -import org.neo4j.cursor.GenericCursor; -import org.neo4j.cursor.IntValue; -import org.neo4j.kernel.api.StatementConstants; -import org.neo4j.kernel.api.cursor.NodeItemHelper; -import org.neo4j.kernel.api.txstate.TransactionState; -import org.neo4j.kernel.impl.util.Cursors; -import org.neo4j.storageengine.api.DegreeItem; -import org.neo4j.storageengine.api.Direction; -import org.neo4j.storageengine.api.LabelItem; -import org.neo4j.storageengine.api.NodeItem; -import org.neo4j.storageengine.api.PropertyItem; -import org.neo4j.storageengine.api.RelationshipItem; -import org.neo4j.storageengine.api.txstate.NodeState; - -/** - * Overlays transaction state on a {@link NodeItem} cursor. - */ -public abstract class TxAbstractNodeCursor extends NodeItemHelper implements Cursor -{ - protected final TransactionState state; - private final Consumer cache; - - protected Cursor cursor; - - protected long id = StatementConstants.NO_SUCH_NODE; - - protected NodeState nodeState; - protected boolean nodeIsAddedInThisTx; - - public TxAbstractNodeCursor( TransactionState state, Consumer cache ) - { - this.state = state; - this.cache = cache; - } - - public TxAbstractNodeCursor init( Cursor nodeCursor ) - { - this.cursor = nodeCursor; - return this; - } - - @Override - public NodeItem get() - { - if ( id == StatementConstants.NO_SUCH_NODE ) - { - throw new IllegalStateException(); - } - - return this; - } - - @Override - public void close() - { - cursor.close(); - cursor = null; - cache.accept( this ); - } - - @Override - public long id() - { - return id; - } - - @Override - public Cursor labels() - { - return state.augmentLabelCursor( nodeIsAddedInThisTx ? Cursors.empty() : cursor.get().labels(), - nodeState ); - } - - @Override - public Cursor label( int labelId ) - { - return state.augmentSingleLabelCursor( - nodeIsAddedInThisTx ? Cursors.empty() : cursor.get().label( labelId ), - nodeState, labelId ); - } - - @Override - public Cursor properties() - { - return state.augmentPropertyCursor( - nodeIsAddedInThisTx ? Cursors.empty() : cursor.get().properties(), - nodeState ); - } - - @Override - public Cursor property( int propertyKeyId ) - { - return state.augmentSinglePropertyCursor( - nodeIsAddedInThisTx ? Cursors.empty() : cursor.get().property( propertyKeyId ), - nodeState, propertyKeyId ); - } - - @Override - public Cursor relationships( Direction direction, int... relTypes ) - { - return state.augmentNodeRelationshipCursor( - nodeIsAddedInThisTx ? Cursors.empty() : cursor.get().relationships( direction, - relTypes ), nodeState, - direction, relTypes ); - } - - @Override - public Cursor relationships( Direction direction ) - { - return state.augmentNodeRelationshipCursor( - nodeIsAddedInThisTx ? Cursors.empty() : cursor.get().relationships( direction ), - nodeState, - direction, null ); - } - - @Override - public Cursor relationshipTypes() - { - if ( nodeIsAddedInThisTx ) - { - return new RelationshipTypeCursor( nodeState.relationshipTypes() ); - } - - PrimitiveIntSet types = Primitive.intSet(); - - // Add types in the current transaction - PrimitiveIntIterator typesInTx = nodeState.relationshipTypes(); - while ( typesInTx.hasNext() ) - { - types.add( typesInTx.next() ); - } - - // Augment with types stored on disk, minus any types where all rels of that type are deleted - // in current tx. - try ( Cursor storeTypes = cursor.get().relationshipTypes() ) - { - while ( storeTypes.next() ) - { - int current = storeTypes.get().getAsInt(); - if ( !types.contains( current ) && degree( Direction.BOTH, current ) > 0 ) - { - types.add( current ); - } - } - } - return new RelationshipTypeCursor( types.iterator() ); - } - - @Override - public int degree( Direction direction ) - { - return nodeState.augmentDegree( direction, nodeIsAddedInThisTx ? 0 : cursor.get().degree( direction ) ); - } - - @Override - public int degree( Direction direction, int relType ) - { - return nodeState.augmentDegree( direction, nodeIsAddedInThisTx ? 0 : cursor.get().degree( direction, relType ), - relType ); - } - - @Override - public Cursor degrees() - { - return new DegreeCursor( relationshipTypes() ); - } - - @Override - public boolean isDense() - { - return cursor.get().isDense(); - } - - private class RelationshipTypeCursor extends GenericCursor - { - private final PrimitiveIntIterator primitiveIntIterator; - - public RelationshipTypeCursor( PrimitiveIntIterator primitiveIntIterator ) - { - this.primitiveIntIterator = primitiveIntIterator; - current = new IntValue(); - } - - @Override - public boolean next() - { - if ( primitiveIntIterator.hasNext() ) - { - ((IntValue) current).setValue( primitiveIntIterator.next() ); - return true; - } - else - { - current = null; - return false; - } - } - } - - private class DegreeCursor implements Cursor, DegreeItem - { - private final Cursor relTypeCursor; - private int type; - private long outgoing; - private long incoming; - - public DegreeCursor( Cursor relTypeCursor ) - { - this.relTypeCursor = relTypeCursor; - } - - @Override - public boolean next() - { - if ( relTypeCursor.next() ) - { - type = relTypeCursor.get().getAsInt(); - outgoing = degree( Direction.OUTGOING, type ); - incoming = degree( Direction.INCOMING, type ); - - return true; - } - else - { - return false; - } - } - - @Override - public void close() - { - relTypeCursor.close(); - } - - @Override - public int type() - { - return type; - } - - @Override - public long outgoing() - { - return outgoing; - } - - @Override - public long incoming() - { - return incoming; - } - - @Override - public DegreeItem get() - { - return this; - } - } -} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/cursor/TxSingleNodeCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/cursor/TxSingleNodeCursor.java index f7cff3d741605..640b25327acf8 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/cursor/TxSingleNodeCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/cursor/TxSingleNodeCursor.java @@ -20,26 +20,61 @@ package org.neo4j.kernel.impl.api.cursor; import java.util.function.Consumer; +import java.util.function.IntSupplier; +import org.neo4j.collection.primitive.Primitive; +import org.neo4j.collection.primitive.PrimitiveIntIterator; +import org.neo4j.collection.primitive.PrimitiveIntSet; import org.neo4j.cursor.Cursor; import org.neo4j.kernel.api.StatementConstants; +import org.neo4j.kernel.api.cursor.EntityItemHelper; import org.neo4j.kernel.api.txstate.TransactionState; +import org.neo4j.storageengine.api.DegreeItem; +import org.neo4j.storageengine.api.Direction; +import org.neo4j.storageengine.api.LabelItem; import org.neo4j.storageengine.api.NodeItem; +import org.neo4j.storageengine.api.PropertyItem; +import org.neo4j.storageengine.api.RelationshipItem; +import org.neo4j.storageengine.api.RelationshipTypeItem; +import org.neo4j.storageengine.api.txstate.NodeState; + +import static org.neo4j.kernel.impl.util.Cursors.empty; /** * Overlays transaction state on a {@link NodeItem} cursor. */ -public class TxSingleNodeCursor extends TxAbstractNodeCursor +public class TxSingleNodeCursor extends EntityItemHelper implements Cursor, NodeItem { + private final TransactionState state; + private final Consumer cache; + + private long id = StatementConstants.NO_SUCH_NODE; + private Cursor cursor; + private NodeState nodeState; + private boolean nodeIsAddedInThisTx; + public TxSingleNodeCursor( TransactionState state, Consumer cache ) { - super( state, (Consumer) cache ); + this.state = state; + this.cache = cache; } public TxSingleNodeCursor init( Cursor nodeCursor, long nodeId ) { this.id = nodeId; - super.init( nodeCursor ); + this.cursor = nodeCursor; + this.nodeIsAddedInThisTx = state.nodeIsAddedInThisTx( id ); + return this; + } + + @Override + public NodeItem get() + { + if ( id == StatementConstants.NO_SUCH_NODE ) + { + throw new IllegalStateException(); + } + return this; } @@ -51,17 +86,16 @@ public boolean next() return false; } - boolean exists = cursor.next(); - if ( state.nodeIsDeletedInThisTx( id ) ) { this.id = StatementConstants.NO_SUCH_NODE; return false; } - this.nodeIsAddedInThisTx = state.nodeIsAddedInThisTx( id ); - if ( exists || nodeIsAddedInThisTx ) + if ( cursor.next() || nodeIsAddedInThisTx ) { + // this makes sure we read the node from tx state only once and we do not loop forever + nodeIsAddedInThisTx = false; this.nodeState = state.getNodeState( id ); return true; } @@ -73,4 +107,224 @@ public boolean next() } } + @Override + public void close() + { + cursor.close(); + cursor = null; + cache.accept( this ); + } + + @Override + public long id() + { + return id; + } + + @Override + public Cursor labels() + { + Cursor cursor = nodeIsAddedInThisTx ? empty() : this.cursor.get().labels(); + return state.augmentLabelCursor( cursor, nodeState ); + } + + @Override + public Cursor label( int labelId ) + { + Cursor cursor = nodeIsAddedInThisTx ? empty() : this.cursor.get().label( labelId ); + return state.augmentSingleLabelCursor( cursor, nodeState, labelId ); + } + + @Override + public boolean hasLabel( int labelId ) + { + return label( labelId ).exists(); + } + + @Override + public Cursor properties() + { + return state.augmentPropertyCursor( nodeIsAddedInThisTx ? empty() : cursor.get().properties(), nodeState ); + } + + @Override + public Cursor property( int propertyKeyId ) + { + Cursor cursor = nodeIsAddedInThisTx ? empty() : this.cursor.get().property( propertyKeyId ); + return state.augmentSinglePropertyCursor( cursor, nodeState, propertyKeyId ); + } + + @Override + public Cursor relationships( Direction direction, int... relTypes ) + { + Cursor cursor = + nodeIsAddedInThisTx ? empty() : this.cursor.get().relationships( direction, relTypes ); + return state.augmentNodeRelationshipCursor( cursor, nodeState, direction, relTypes ); + } + + @Override + public Cursor relationships( Direction direction ) + { + Cursor cursor = nodeIsAddedInThisTx ? empty() : this.cursor.get().relationships( direction ); + return state.augmentNodeRelationshipCursor( cursor, nodeState, direction, null ); + } + + @Override + public Cursor relationshipTypes() + { + if ( nodeIsAddedInThisTx ) + { + return new RelationshipTypeCursor( nodeState.relationshipTypes() ); + } + + PrimitiveIntSet types = Primitive.intSet(); + + // Add types in the current transaction + PrimitiveIntIterator typesInTx = nodeState.relationshipTypes(); + while ( typesInTx.hasNext() ) + { + types.add( typesInTx.next() ); + } + + // Augment with types stored on disk, minus any types where all rels of that type are deleted + // in current tx. + try ( Cursor storeTypes = cursor.get().relationshipTypes() ) + { + while ( storeTypes.next() ) + { + int current = storeTypes.get().getAsInt(); + if ( !types.contains( current ) && degree( Direction.BOTH, current ) > 0 ) + { + types.add( current ); + } + } + } + return new RelationshipTypeCursor( types.iterator() ); + } + + @Override + public int degree( Direction direction ) + { + return nodeState.augmentDegree( direction, nodeIsAddedInThisTx ? 0 : cursor.get().degree( direction ) ); + } + + @Override + public int degree( Direction direction, int relType ) + { + int degree = nodeIsAddedInThisTx ? 0 : cursor.get().degree( direction, relType ); + return nodeState.augmentDegree( direction, degree, relType ); + } + + @Override + public Cursor degrees() + { + return new DegreeCursor( relationshipTypes() ); + } + + @Override + public boolean isDense() + { + return cursor.get().isDense(); + } + + private class RelationshipTypeCursor implements Cursor, RelationshipTypeItem + { + private final PrimitiveIntIterator primitiveIntIterator; + private int current = StatementConstants.NO_SUCH_RELATIONSHIP_TYPE; + + RelationshipTypeCursor( PrimitiveIntIterator primitiveIntIterator ) + { + this.primitiveIntIterator = primitiveIntIterator; + } + + @Override + public boolean next() + { + if ( primitiveIntIterator.hasNext() ) + { + current = primitiveIntIterator.next(); + return true; + } + + current = StatementConstants.NO_SUCH_RELATIONSHIP_TYPE; + return false; + } + + @Override + public void close() + { + } + + @Override + public int getAsInt() + { + return current; + } + + @Override + public RelationshipTypeItem get() + { + return this; + } + } + + private class DegreeCursor implements Cursor, DegreeItem + { + private final Cursor relTypeCursor; + private int type; + private long outgoing; + private long incoming; + + DegreeCursor( Cursor relTypeCursor ) + { + this.relTypeCursor = relTypeCursor; + } + + @Override + public boolean next() + { + if ( relTypeCursor.next() ) + { + type = relTypeCursor.get().getAsInt(); + outgoing = degree( Direction.OUTGOING, type ); + incoming = degree( Direction.INCOMING, type ); + + return true; + } + else + { + return false; + } + } + + @Override + public void close() + { + relTypeCursor.close(); + } + + @Override + public int type() + { + return type; + } + + @Override + public long outgoing() + { + return outgoing; + } + + @Override + public long incoming() + { + return incoming; + } + + @Override + public DegreeItem get() + { + return this; + } + } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/AllRecordIdIterator.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/AllNodeIterator.java similarity index 65% rename from community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/AllRecordIdIterator.java rename to community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/AllNodeIterator.java index b48f0fad2ec17..6b685b2585227 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/AllRecordIdIterator.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/AllNodeIterator.java @@ -19,20 +19,15 @@ */ package org.neo4j.kernel.impl.api.store; -import org.neo4j.kernel.impl.store.CommonAbstractStore; -import org.neo4j.kernel.impl.store.record.PrimitiveRecord; -import org.neo4j.kernel.impl.store.record.RecordLoad; +import org.neo4j.kernel.impl.store.NodeStore; -public class AllRecordIdIterator> - extends HighIdAwareIterator +public class AllNodeIterator extends HighIdAwareIterator { - protected final RECORD record; private long currentId; - AllRecordIdIterator( RECORD record, STORE store ) + AllNodeIterator( NodeStore nodeStore ) { - super( store ); - this.record = record; + super( nodeStore ); } @Override @@ -42,10 +37,9 @@ protected boolean doFetchNext( long highId ) { try { - store.getRecord( currentId, record, RecordLoad.CHECK ); - if ( record.inUse() ) + if ( store.isInUse( currentId ) ) { - return next( record.getId() ); + return next( currentId ); } } finally diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/AllRelationshipIterator.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/AllRelationshipIterator.java index 3f7c8d0ca702a..4dc1371b925a1 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/AllRelationshipIterator.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/AllRelationshipIterator.java @@ -21,14 +21,20 @@ import org.neo4j.kernel.impl.api.RelationshipVisitor; import org.neo4j.kernel.impl.store.RelationshipStore; +import org.neo4j.kernel.impl.store.record.RecordLoad; import org.neo4j.kernel.impl.store.record.RelationshipRecord; -public class AllRelationshipIterator extends AllRecordIdIterator +public class AllRelationshipIterator extends HighIdAwareIterator implements RelationshipIterator { - AllRelationshipIterator( RelationshipRecord record, RelationshipStore store ) + private final RelationshipRecord record; + + private long currentId; + + AllRelationshipIterator( RelationshipStore store ) { - super( record, store ); + super( store ); + this.record = store.newRecord(); } @Override @@ -38,4 +44,25 @@ public boolean relationshipVisit( long relationshi visitor.visit( relationshipId, record.getType(), record.getFirstNode(), record.getSecondNode() ); return false; } + + @Override + protected boolean doFetchNext( long highId ) + { + while ( currentId <= highId ) + { + try + { + store.getRecord( currentId, record, RecordLoad.CHECK ); + if ( record.inUse() ) + { + return next( record.getId() ); + } + } + finally + { + currentId++; + } + } + return false; + } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DegreeCounter.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DegreeCounter.java new file mode 100644 index 0000000000000..98a518dbb4976 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DegreeCounter.java @@ -0,0 +1,108 @@ +/* + * 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.api.store; + +import org.neo4j.kernel.impl.store.InvalidRecordException; +import org.neo4j.kernel.impl.store.RecordCursor; +import org.neo4j.kernel.impl.store.RecordCursors; +import org.neo4j.kernel.impl.store.record.NodeRecord; +import org.neo4j.kernel.impl.store.record.Record; +import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; +import org.neo4j.kernel.impl.store.record.RelationshipRecord; +import org.neo4j.storageengine.api.Direction; + +import static org.neo4j.kernel.impl.store.record.RecordLoad.CHECK; +import static org.neo4j.kernel.impl.store.record.RecordLoad.FORCE; + +class DegreeCounter +{ + private DegreeCounter() + { + } + + static long countByFirstPrevPointer( long relationshipId, RecordCursor cursor, + NodeRecord nodeRecord, RelationshipRecord relationshipRecord ) + { + if ( relationshipId == Record.NO_NEXT_RELATIONSHIP.intValue() ) + { + return 0; + } + cursor.next( relationshipId, relationshipRecord, FORCE ); + if ( relationshipRecord.getFirstNode() == nodeRecord.getId() ) + { + return relationshipRecord.getFirstPrevRel(); + } + if ( relationshipRecord.getSecondNode() == nodeRecord.getId() ) + { + return relationshipRecord.getSecondPrevRel(); + } + throw new InvalidRecordException( "Node " + nodeRecord.getId() + " neither start nor end node of " + relationshipRecord ); + } + + static int countRelationshipsInGroup( long groupId, Direction direction, Integer type, NodeRecord nodeRecord, + RelationshipRecord relationshipRecord, RelationshipGroupRecord groupRecord, RecordCursors cursors ) + { + int count = 0; + while ( groupId != Record.NO_NEXT_RELATIONSHIP.intValue() ) + { + boolean groupRecordInUse = cursors.relationshipGroup().next( groupId, groupRecord, FORCE ); + if ( groupRecordInUse && ( type == null || groupRecord.getType() == type ) ) + { + count += nodeDegreeByDirection( direction, nodeRecord, relationshipRecord, groupRecord, cursors ); + if ( type != null ) + { + // we have read the only type we were interested on, so break the look + break; + } + } + groupId = groupRecord.getNext(); + } + return count; + } + + private static long nodeDegreeByDirection( Direction direction, NodeRecord nodeRecord, + RelationshipRecord relationshipRecord, RelationshipGroupRecord groupRecord, RecordCursors cursors ) + { + RecordCursor cursor = cursors.relationship(); + long loopCount = countByFirstPrevPointer( groupRecord.getFirstLoop(), cursor, nodeRecord, relationshipRecord ); + switch ( direction ) + { + case OUTGOING: + { + long firstOut = groupRecord.getFirstOut(); + return countByFirstPrevPointer( firstOut, cursor, nodeRecord, relationshipRecord ) + loopCount; + } + case INCOMING: + { + long firstIn = groupRecord.getFirstIn(); + return countByFirstPrevPointer( firstIn, cursor, nodeRecord, relationshipRecord ) + loopCount; + } + case BOTH: + { + long firstOut = groupRecord.getFirstOut(); + long firstIn = groupRecord.getFirstIn(); + return countByFirstPrevPointer( firstOut, cursor, nodeRecord, relationshipRecord ) + + countByFirstPrevPointer( firstIn, cursor, nodeRecord, relationshipRecord ) + loopCount; + } + default: + throw new IllegalArgumentException( direction.name() ); + } + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DegreeItemCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DegreeItemCursor.java new file mode 100644 index 0000000000000..0fb6b2163fd0d --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DegreeItemCursor.java @@ -0,0 +1,91 @@ +/* + * 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.api.store; + +import org.neo4j.collection.primitive.PrimitiveIntIterator; +import org.neo4j.collection.primitive.PrimitiveIntObjectMap; +import org.neo4j.cursor.Cursor; +import org.neo4j.storageengine.api.DegreeItem; + +class DegreeItemCursor implements Cursor, DegreeItem +{ + private final PrimitiveIntObjectMap degrees; + private PrimitiveIntIterator keys; + + private int type; + private int outgoing; + private int incoming; + + DegreeItemCursor( PrimitiveIntObjectMap degrees ) + { + this.keys = degrees.iterator(); + this.degrees = degrees; + } + + @Override + public void close() + { + keys = null; + } + + @Override + public int type() + { + return type; + } + + @Override + public long outgoing() + { + return outgoing; + } + + @Override + public long incoming() + { + return incoming; + } + + @Override + public DegreeItem get() + { + if ( keys == null ) + { + throw new IllegalStateException( "No next item found" ); + } + return this; + } + + @Override + public boolean next() + { + if ( keys != null && keys.hasNext() ) + { + type = keys.next(); + int[] degreeValues = degrees.get( type ); + outgoing = degreeValues[0] + degreeValues[2]; + incoming = degreeValues[1] + degreeValues[2]; + + return true; + } + keys = null; + return false; + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DegreeItemDenseCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DegreeItemDenseCursor.java new file mode 100644 index 0000000000000..e3d03cd65f6f9 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DegreeItemDenseCursor.java @@ -0,0 +1,114 @@ +/* + * 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.api.store; + +import org.neo4j.cursor.Cursor; +import org.neo4j.kernel.impl.store.RecordCursor; +import org.neo4j.kernel.impl.store.RecordCursors; +import org.neo4j.kernel.impl.store.record.NodeRecord; +import org.neo4j.kernel.impl.store.record.Record; +import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; +import org.neo4j.kernel.impl.store.record.RelationshipRecord; +import org.neo4j.storageengine.api.DegreeItem; + +import static org.neo4j.kernel.impl.api.store.DegreeCounter.countByFirstPrevPointer; +import static org.neo4j.kernel.impl.store.record.RecordLoad.FORCE; + +class DegreeItemDenseCursor implements Cursor, DegreeItem +{ + private final NodeRecord nodeRecord; + private final RelationshipGroupRecord relationshipGroupRecord; + private final RelationshipRecord relationshipRecord; + private final RecordCursors recordCursors; + + private long groupId; + private int type; + private long outgoing; + private long incoming; + + DegreeItemDenseCursor( long groupId, + NodeRecord nodeRecord, + RelationshipGroupRecord relationshipGroupRecord, + RelationshipRecord relationshipRecord, + RecordCursors recordCursors ) + { + this.groupId = groupId; + this.nodeRecord = nodeRecord; + this.relationshipGroupRecord = relationshipGroupRecord; + this.relationshipRecord = relationshipRecord; + this.recordCursors = recordCursors; + } + + @Override + public boolean next() + { + while ( groupId != Record.NO_NEXT_RELATIONSHIP.intValue() ) + { + boolean groupRecordInUse = recordCursors.relationshipGroup().next( groupId, relationshipGroupRecord, FORCE ); + groupId = relationshipGroupRecord.getNext(); + if ( groupRecordInUse ) + { + this.type = relationshipGroupRecord.getType(); + + long firstLoop = relationshipGroupRecord.getFirstLoop(); + long firstOut = relationshipGroupRecord.getFirstOut(); + long firstIn = relationshipGroupRecord.getFirstIn(); + + RecordCursor relationshipCursor = recordCursors.relationship(); + long loop = countByFirstPrevPointer( firstLoop, relationshipCursor, nodeRecord, relationshipRecord ); + this.outgoing = + countByFirstPrevPointer( firstOut, relationshipCursor, nodeRecord, relationshipRecord ) + loop; + this.incoming = + countByFirstPrevPointer( firstIn, relationshipCursor, nodeRecord, relationshipRecord ) + loop; + return true; + } + } + return false; + } + + @Override + public void close() + { + } + + @Override + public DegreeItem get() + { + return this; + } + + @Override + public int type() + { + return type; + } + + @Override + public long outgoing() + { + return outgoing; + } + + @Override + public long incoming() + { + return incoming; + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DiskLayer.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DiskLayer.java index 18285048632a9..3443fd3c55f71 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DiskLayer.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/DiskLayer.java @@ -58,7 +58,6 @@ import org.neo4j.kernel.impl.store.counts.CountsTracker; import org.neo4j.kernel.impl.store.record.IndexRule; import org.neo4j.kernel.impl.store.record.NodePropertyConstraintRule; -import org.neo4j.kernel.impl.store.record.NodeRecord; import org.neo4j.kernel.impl.store.record.PropertyConstraintRule; import org.neo4j.kernel.impl.store.record.RelationshipPropertyConstraintRule; import org.neo4j.kernel.impl.store.record.RelationshipRecord; @@ -74,7 +73,7 @@ import org.neo4j.storageengine.api.schema.PopulationProgress; import org.neo4j.storageengine.api.schema.SchemaRule; -import static org.neo4j.kernel.impl.store.record.RecordLoad.FORCE; +import static org.neo4j.kernel.impl.store.record.RecordLoad.CHECK; import static org.neo4j.register.Registers.newDoubleLongRegister; /** @@ -413,8 +412,8 @@ public void relationshipVisit( long relationshipId RelationshipVisitor relationshipVisitor ) throws EntityNotFoundException, EXCEPTION { // TODO Please don't create a record for this, it's ridiculous - RelationshipRecord record = relationshipStore.newRecord(); - if ( !relationshipStore.getRecord( relationshipId, record, FORCE ).inUse() ) + RelationshipRecord record = relationshipStore.getRecord( relationshipId, relationshipStore.newRecord(), CHECK ); + if ( !record.inUse() ) { throw new EntityNotFoundException( EntityType.RELATIONSHIP, relationshipId ); } @@ -424,13 +423,13 @@ public void relationshipVisit( long relationshipId @Override public PrimitiveLongIterator nodesGetAll() { - return new AllRecordIdIterator<>( new NodeRecord( -1 ), nodeStore ); + return new AllNodeIterator( nodeStore ); } @Override public RelationshipIterator relationshipsGetAll() { - return new AllRelationshipIterator( new RelationshipRecord( -1 ), relationshipStore ); + return new AllRelationshipIterator( relationshipStore ); } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/NodeExploringCursors.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/NodeExploringCursors.java new file mode 100644 index 0000000000000..3ce18b52a8629 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/NodeExploringCursors.java @@ -0,0 +1,139 @@ +/* + * 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.api.store; + +import org.neo4j.collection.primitive.PrimitiveIntObjectMap; +import org.neo4j.cursor.Cursor; +import org.neo4j.kernel.impl.locking.Lock; +import org.neo4j.kernel.impl.locking.LockService; +import org.neo4j.kernel.impl.store.RecordCursors; +import org.neo4j.kernel.impl.store.RecordStore; +import org.neo4j.kernel.impl.store.RelationshipStore; +import org.neo4j.kernel.impl.store.record.NodeRecord; +import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; +import org.neo4j.kernel.impl.util.InstanceCache; +import org.neo4j.storageengine.api.DegreeItem; +import org.neo4j.storageengine.api.Direction; +import org.neo4j.storageengine.api.LabelItem; +import org.neo4j.storageengine.api.PropertyItem; +import org.neo4j.storageengine.api.RelationshipItem; + +class NodeExploringCursors +{ + private final InstanceCache labelCursorCache; + private final InstanceCache singleLabelCursorCache; + private final InstanceCache nodeRelationshipCursorCache; + private final InstanceCache singlePropertyCursorCache; + private final InstanceCache propertyCursorCache; + + private final RelationshipStore relationshipStore; + private final RecordStore relationshipGroupStore; + + NodeExploringCursors( final RecordCursors cursors, final LockService lockService, + final RelationshipStore relationshipStore, + final RecordStore relationshipGroupStore ) + { + labelCursorCache = new InstanceCache() + { + @Override + protected StoreLabelCursor create() + { + return new StoreLabelCursor( cursors.label(), labelCursorCache ); + } + }; + singleLabelCursorCache = new InstanceCache() + { + @Override + protected StoreSingleLabelCursor create() + { + return new StoreSingleLabelCursor( cursors.label(), singleLabelCursorCache ); + } + }; + this.relationshipStore = relationshipStore; + this.relationshipGroupStore = relationshipGroupStore; + nodeRelationshipCursorCache = new InstanceCache() + { + @Override + protected StoreNodeRelationshipCursor create() + { + return new StoreNodeRelationshipCursor( relationshipStore.newRecord(), + relationshipGroupStore.newRecord(), nodeRelationshipCursorCache, cursors, lockService ); + } + }; + singlePropertyCursorCache = new InstanceCache() + { + @Override + protected StoreSinglePropertyCursor create() + { + return new StoreSinglePropertyCursor( cursors, singlePropertyCursorCache ); + } + }; + propertyCursorCache = new InstanceCache() + { + @Override + protected StorePropertyCursor create() + { + return new StorePropertyCursor( cursors, propertyCursorCache ); + } + }; + } + + public Cursor properties( long nextProp, Lock lock ) + { + return propertyCursorCache.get().init( nextProp, lock ); + } + + public Cursor property( long nextProp, int propertyKeyId, Lock lock ) + { + return singlePropertyCursorCache.get().init( nextProp, propertyKeyId, lock ); + } + + public Cursor labels( NodeRecord nodeRecord ) + { + return labelCursorCache.get().init( nodeRecord ); + } + + public Cursor label( NodeRecord nodeRecord, int labelId ) + { + return singleLabelCursorCache.get().init( nodeRecord, labelId ); + } + + public Cursor relationships( boolean dense, long nextRel, long id, Direction direction ) + { + return nodeRelationshipCursorCache.get().init( dense, nextRel, id, direction ); + } + + public Cursor relationships( boolean dense, long nextRel, long id, Direction direction, + int... relTypes ) + { + return nodeRelationshipCursorCache.get().init( dense, nextRel, id, direction, relTypes ); + } + + public Cursor degrees( PrimitiveIntObjectMap degrees ) + { + return new DegreeItemCursor( degrees ); + } + + public Cursor degrees( long groupId, NodeRecord nodeRecord, RecordCursors recordCursors ) + { + return new DegreeItemDenseCursor( groupId, nodeRecord, relationshipGroupStore.newRecord(), + relationshipStore.newRecord(), recordCursors ); + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/RelationshipTypeCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/RelationshipTypeCursor.java new file mode 100644 index 0000000000000..10c41b9234ca6 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/RelationshipTypeCursor.java @@ -0,0 +1,77 @@ +/* + * 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.api.store; + +import org.neo4j.collection.primitive.Primitive; +import org.neo4j.collection.primitive.PrimitiveIntSet; +import org.neo4j.cursor.Cursor; +import org.neo4j.storageengine.api.RelationshipItem; +import org.neo4j.storageengine.api.RelationshipTypeItem; + +import static org.neo4j.kernel.api.StatementConstants.NO_SUCH_RELATIONSHIP_TYPE; + +class RelationshipTypeCursor implements Cursor, RelationshipTypeItem +{ + private final PrimitiveIntSet foundTypes; + private final Cursor relationships; + + private int value = NO_SUCH_RELATIONSHIP_TYPE; + + RelationshipTypeCursor( Cursor relationships ) + { + this.relationships = relationships; + foundTypes = Primitive.intSet( 5 ); + } + + @Override + public boolean next() + { + while ( relationships.next() ) + { + if ( !foundTypes.contains( relationships.get().type() ) ) + { + foundTypes.add( relationships.get().type() ); + value = relationships.get().type(); + return true; + } + } + + value = NO_SUCH_RELATIONSHIP_TYPE; + return false; + } + + @Override + public void close() + { + relationships.close(); + } + + @Override + public RelationshipTypeItem get() + { + return this; + } + + @Override + public int getAsInt() + { + return value; + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/RelationshipTypeDenseCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/RelationshipTypeDenseCursor.java new file mode 100644 index 0000000000000..6f26e4c10154d --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/RelationshipTypeDenseCursor.java @@ -0,0 +1,80 @@ +/* + * 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.api.store; + +import org.neo4j.cursor.Cursor; +import org.neo4j.kernel.impl.store.RecordCursors; +import org.neo4j.kernel.impl.store.record.Record; +import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; +import org.neo4j.storageengine.api.RelationshipTypeItem; + +import static org.neo4j.kernel.api.StatementConstants.NO_SUCH_RELATIONSHIP_TYPE; +import static org.neo4j.kernel.impl.store.record.RecordLoad.FORCE; + +class RelationshipTypeDenseCursor implements Cursor, RelationshipTypeItem +{ + private final RelationshipGroupRecord groupRecord; + private RecordCursors recordCursors; + + private long groupId; + private int value = NO_SUCH_RELATIONSHIP_TYPE; + + RelationshipTypeDenseCursor( long groupId, RelationshipGroupRecord groupRecord, RecordCursors recordCursors ) + { + this.groupId = groupId; + this.groupRecord = groupRecord; + this.recordCursors = recordCursors; + } + + @Override + public boolean next() + { + while ( groupId != Record.NO_NEXT_RELATIONSHIP.intValue() ) + { + boolean groupRecordInUse = recordCursors.relationshipGroup().next( groupId, groupRecord, FORCE ); + groupId = groupRecord.getNext(); + if ( groupRecordInUse ) + { + value = groupRecord.getType(); + return true; + } + } + + value = NO_SUCH_RELATIONSHIP_TYPE; + return false; + } + + @Override + public void close() + { + } + + @Override + public RelationshipTypeItem get() + { + return this; + } + + @Override + public int getAsInt() + { + return value; + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreAbstractNodeCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreAbstractNodeCursor.java deleted file mode 100644 index 2294977a5ab97..0000000000000 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreAbstractNodeCursor.java +++ /dev/null @@ -1,577 +0,0 @@ -/* - * 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.api.store; - -import java.util.function.IntSupplier; - -import org.neo4j.collection.primitive.Primitive; -import org.neo4j.collection.primitive.PrimitiveIntIterator; -import org.neo4j.collection.primitive.PrimitiveIntObjectMap; -import org.neo4j.collection.primitive.PrimitiveIntSet; -import org.neo4j.cursor.Cursor; -import org.neo4j.cursor.IntValue; -import org.neo4j.kernel.api.cursor.NodeItemHelper; -import org.neo4j.kernel.impl.locking.Lock; -import org.neo4j.kernel.impl.locking.LockService; -import org.neo4j.kernel.impl.store.InvalidRecordException; -import org.neo4j.kernel.impl.store.NeoStores; -import org.neo4j.kernel.impl.store.NodeStore; -import org.neo4j.kernel.impl.store.RecordCursors; -import org.neo4j.kernel.impl.store.RecordStore; -import org.neo4j.kernel.impl.store.RelationshipStore; -import org.neo4j.kernel.impl.store.record.NodeRecord; -import org.neo4j.kernel.impl.store.record.Record; -import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; -import org.neo4j.kernel.impl.store.record.RelationshipRecord; -import org.neo4j.kernel.impl.util.InstanceCache; -import org.neo4j.storageengine.api.DegreeItem; -import org.neo4j.storageengine.api.Direction; -import org.neo4j.storageengine.api.LabelItem; -import org.neo4j.storageengine.api.NodeItem; -import org.neo4j.storageengine.api.PropertyItem; -import org.neo4j.storageengine.api.RelationshipItem; - -import static org.neo4j.kernel.impl.locking.LockService.NO_LOCK_SERVICE; -import static org.neo4j.kernel.impl.store.record.RecordLoad.CHECK; -import static org.neo4j.kernel.impl.store.record.RecordLoad.FORCE; - -/** - * Base cursor for nodes. - */ -public abstract class StoreAbstractNodeCursor extends NodeItemHelper implements Cursor -{ - protected final NodeRecord nodeRecord; - protected final NodeStore nodeStore; - protected final RelationshipStore relationshipStore; - protected final RecordStore relationshipGroupStore; - - protected final StoreStatement storeStatement; - - private final LockService lockService; - private final InstanceCache labelCursor; - private final InstanceCache singleLabelCursor; - private final InstanceCache nodeRelationshipCursor; - private final InstanceCache singlePropertyCursor; - private final InstanceCache allPropertyCursor; - protected final RecordCursors cursors; - - StoreAbstractNodeCursor( NodeRecord nodeRecord, - final NeoStores neoStores, - final StoreStatement storeStatement, - final RecordCursors cursors, - final LockService lockService ) - { - this.nodeRecord = nodeRecord; - this.cursors = cursors; - this.nodeStore = neoStores.getNodeStore(); - this.relationshipStore = neoStores.getRelationshipStore(); - this.relationshipGroupStore = neoStores.getRelationshipGroupStore(); - this.storeStatement = storeStatement; - this.lockService = lockService; - - labelCursor = new InstanceCache() - { - @Override - protected StoreLabelCursor create() - { - return new StoreLabelCursor( cursors.label(), this ); - } - }; - singleLabelCursor = new InstanceCache() - { - @Override - protected StoreSingleLabelCursor create() - { - return new StoreSingleLabelCursor( cursors.label(), this ); - } - }; - nodeRelationshipCursor = new InstanceCache() - { - @Override - protected StoreNodeRelationshipCursor create() - { - return new StoreNodeRelationshipCursor( relationshipStore.newRecord(), - relationshipGroupStore.newRecord(), this, cursors, lockService ); - } - }; - singlePropertyCursor = new InstanceCache() - { - @Override - protected StoreSinglePropertyCursor create() - { - return new StoreSinglePropertyCursor( cursors, this ); - } - }; - allPropertyCursor = new InstanceCache() - { - @Override - protected StorePropertyCursor create() - { - return new StorePropertyCursor( cursors, allPropertyCursor ); - } - }; - } - - @Override - public NodeItem get() - { - return this; - } - - @Override - public long id() - { - return nodeRecord.getId(); - } - - @Override - public Cursor labels() - { - return labelCursor.get().init( nodeRecord ); - } - - @Override - public Cursor label( int labelId ) - { - return singleLabelCursor.get().init( nodeRecord, labelId ); - } - - private Lock shortLivedReadLock() - { - Lock lock = lockService.acquireNodeLock( nodeRecord.getId(), LockService.LockType.READ_LOCK ); - if ( lockService != NO_LOCK_SERVICE ) - { - boolean success = false; - try - { - // It's safer to re-read the node record here, specifically nextProp, after acquiring the lock - if ( !cursors.node().next( nodeRecord.getId(), nodeRecord, CHECK ) ) - { - // So it looks like the node has been deleted. The current behavior of NodeStore#loadRecord - // is to only set the inUse field on loading an unused record. This should (and will) - // change to be more of a centralized behavior by the stores. Anyway, setting this pointer - // to the primitive equivalent of null the property cursor will just look empty from the - // outside and the releasing of the lock will be done as usual. - nodeRecord.setNextProp( Record.NO_NEXT_PROPERTY.intValue() ); - } - success = true; - } - finally - { - if ( !success ) - { - lock.release(); - } - } - } - return lock; - } - - @Override - public Cursor properties() - { - return allPropertyCursor.get().init( nodeRecord.getNextProp(), shortLivedReadLock() ); - } - - @Override - public Cursor property( int propertyKeyId ) - { - return singlePropertyCursor.get().init( nodeRecord.getNextProp(), propertyKeyId, shortLivedReadLock() ); - } - - @Override - public Cursor relationships( Direction direction ) - { - return nodeRelationshipCursor.get().init( nodeRecord.isDense(), nodeRecord.getNextRel(), nodeRecord.getId(), - direction, null ); - } - - @Override - public Cursor relationships( Direction direction, int... relTypes ) - { - return nodeRelationshipCursor.get().init( nodeRecord.isDense(), nodeRecord.getNextRel(), nodeRecord.getId(), - direction, relTypes ); - } - - @Override - public Cursor relationshipTypes() - { - if ( nodeRecord.isDense() ) - { - return new Cursor() - { - private long groupId = nodeRecord.getNextRel(); - private final IntValue value = new IntValue(); - private final RelationshipGroupRecord group = relationshipGroupStore.newRecord(); - - @Override - public boolean next() - { - while ( groupId != Record.NO_NEXT_RELATIONSHIP.intValue() ) - { - boolean groupRecordInUse = cursors.relationshipGroup().next( groupId, group, FORCE ); - groupId = group.getNext(); - if ( groupRecordInUse ) - { - value.setValue( group.getType() ); - return true; - } - } - return false; - } - - @Override - public void close() - { - } - - @Override - public IntSupplier get() - { - return value; - } - }; - } - else - { - final Cursor relationships = relationships( Direction.BOTH ); - return new Cursor() - { - private final PrimitiveIntSet foundTypes = Primitive.intSet( 5 ); - private final IntValue value = new IntValue(); - - @Override - public boolean next() - { - while ( relationships.next() ) - { - if ( !foundTypes.contains( relationships.get().type() ) ) - { - foundTypes.add( relationships.get().type() ); - value.setValue( relationships.get().type() ); - return true; - } - } - - return false; - } - - @Override - public void close() - { - } - - @Override - public IntSupplier get() - { - return value; - } - }; - } - } - - @Override - public int degree( Direction direction ) - { - if ( nodeRecord.isDense() ) - { - long groupId = nodeRecord.getNextRel(); - long count = 0; - RelationshipGroupRecord group = relationshipGroupStore.newRecord(); - RelationshipRecord relationship = relationshipStore.newRecord(); - while ( groupId != Record.NO_NEXT_RELATIONSHIP.intValue() ) - { - boolean groupRecordInUse = cursors.relationshipGroup().next( groupId, group, FORCE ); - if ( groupRecordInUse ) - { - count += nodeDegreeByDirection( group, direction, relationship ); - } - groupId = group.getNext(); - } - return (int) count; - } - else - { - try ( Cursor relationship = relationships( direction ) ) - { - int count = 0; - while ( relationship.next() ) - { - count++; - } - return count; - } - } - } - - @Override - public int degree( Direction direction, int relType ) - { - if ( nodeRecord.isDense() ) - { - long groupId = nodeRecord.getNextRel(); - RelationshipGroupRecord group = relationshipGroupStore.newRecord(); - RelationshipRecord relationship = relationshipStore.newRecord(); - while ( groupId != Record.NO_NEXT_RELATIONSHIP.intValue() ) - { - boolean groupRecordInUse = cursors.relationshipGroup().next( groupId, group, FORCE ); - if ( groupRecordInUse && group.getType() == relType ) - { - return (int) nodeDegreeByDirection( group, direction, relationship ); - } - groupId = group.getNext(); - } - return 0; - } - else - { - try ( Cursor relationship = relationships( direction, relType ) ) - { - int count = 0; - while ( relationship.next() ) - { - count++; - } - return count; - } - } - } - - @Override - public Cursor degrees() - { - if ( nodeRecord.isDense() ) - { - long groupId = nodeRecord.getNextRel(); - return new DegreeItemDenseCursor( groupId ); - } - else - { - final PrimitiveIntObjectMap degrees = Primitive.intObjectMap( 5 ); - - try ( Cursor relationship = relationships( Direction.BOTH ) ) - { - while ( relationship.next() ) - { - RelationshipItem rel = relationship.get(); - - int[] byType = degrees.get( rel.type() ); - if ( byType == null ) - { - degrees.put( rel.type(), byType = new int[3] ); - } - byType[directionOf( nodeRecord.getId(), rel.id(), rel.startNode(), rel.endNode() ).ordinal()]++; - } - } - - final PrimitiveIntIterator keys = degrees.iterator(); - - return new DegreeItemIterator( keys, degrees ); - } - } - - @Override - public boolean isDense() - { - return nodeRecord.isDense(); - } - - private long nodeDegreeByDirection( RelationshipGroupRecord group, Direction direction, - RelationshipRecord relationship ) - { - long loopCount = countByFirstPrevPointer( group.getFirstLoop(), relationship ); - switch ( direction ) - { - case OUTGOING: - return countByFirstPrevPointer( group.getFirstOut(), relationship ) + loopCount; - case INCOMING: - return countByFirstPrevPointer( group.getFirstIn(), relationship ) + loopCount; - case BOTH: - return countByFirstPrevPointer( group.getFirstOut(), relationship ) + - countByFirstPrevPointer( group.getFirstIn(), relationship ) + loopCount; - default: - throw new IllegalArgumentException( direction.name() ); - } - } - - private long countByFirstPrevPointer( long relationshipId, RelationshipRecord record ) - { - if ( relationshipId == Record.NO_NEXT_RELATIONSHIP.intValue() ) - { - return 0; - } - cursors.relationship().next( relationshipId, record, FORCE ); - if ( record.getFirstNode() == nodeRecord.getId() ) - { - return record.getFirstPrevRel(); - } - if ( record.getSecondNode() == nodeRecord.getId() ) - { - return record.getSecondPrevRel(); - } - throw new InvalidRecordException( "Node " + nodeRecord.getId() + " neither start nor end node of " + record ); - } - - private Direction directionOf( long nodeId, long relationshipId, long startNode, long endNode ) - { - if ( startNode == nodeId ) - { - return endNode == nodeId ? Direction.BOTH : Direction.OUTGOING; - } - if ( endNode == nodeId ) - { - return Direction.INCOMING; - } - throw new InvalidRecordException( "Node " + nodeId + " neither start nor end node of relationship " + - relationshipId + " with startNode:" + startNode + " and endNode:" + endNode ); - } - - private static class DegreeItemIterator implements Cursor, DegreeItem - { - private final PrimitiveIntObjectMap degrees; - private PrimitiveIntIterator keys; - - private int type; - private int outgoing; - private int incoming; - - public DegreeItemIterator( PrimitiveIntIterator keys, PrimitiveIntObjectMap degrees ) - { - this.keys = keys; - this.degrees = degrees; - } - - @Override - public void close() - { - keys = null; - } - - @Override - public int type() - { - return type; - } - - @Override - public long outgoing() - { - return outgoing; - } - - @Override - public long incoming() - { - return incoming; - } - - @Override - public DegreeItem get() - { - if ( keys == null ) - { - throw new IllegalStateException(); - } - - return this; - } - - @Override - public boolean next() - { - if ( keys != null && keys.hasNext() ) - { - type = keys.next(); - int[] degreeValues = degrees.get( type ); - outgoing = degreeValues[0] + degreeValues[2]; - incoming = degreeValues[1] + degreeValues[2]; - - return true; - } - keys = null; - return false; - } - } - - private class DegreeItemDenseCursor implements Cursor, DegreeItem - { - private long groupId; - - private int type; - private long outgoing; - private long incoming; - private final RelationshipGroupRecord group = relationshipGroupStore.newRecord(); - private final RelationshipRecord relationship = relationshipStore.newRecord(); - - public DegreeItemDenseCursor( long groupId ) - { - this.groupId = groupId; - } - - @Override - public boolean next() - { - while ( groupId != Record.NO_NEXT_RELATIONSHIP.intValue() ) - { - boolean groupRecordInUse = cursors.relationshipGroup().next( groupId, group, FORCE ); - groupId = group.getNext(); - if ( groupRecordInUse ) - { - this.type = group.getType(); - - long loop = countByFirstPrevPointer( group.getFirstLoop(), relationship ); - this.outgoing = countByFirstPrevPointer( group.getFirstOut(), relationship ) + loop; - this.incoming = countByFirstPrevPointer( group.getFirstIn(), relationship ) + loop; - return true; - } - } - return false; - } - - @Override - public void close() - { - } - - @Override - public DegreeItem get() - { - return this; - } - - @Override - public int type() - { - return type; - } - - @Override - public long outgoing() - { - return outgoing; - } - - @Override - public long incoming() - { - return incoming; - } - } -} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreSingleNodeCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreSingleNodeCursor.java index 8418c93ff4201..6b51c05501007 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreSingleNodeCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreSingleNodeCursor.java @@ -21,31 +21,61 @@ import java.util.function.Consumer; +import org.neo4j.collection.primitive.Primitive; +import org.neo4j.collection.primitive.PrimitiveIntObjectMap; +import org.neo4j.cursor.Cursor; import org.neo4j.kernel.api.StatementConstants; +import org.neo4j.kernel.api.cursor.EntityItemHelper; +import org.neo4j.kernel.impl.locking.Lock; import org.neo4j.kernel.impl.locking.LockService; +import org.neo4j.kernel.impl.store.InvalidRecordException; import org.neo4j.kernel.impl.store.NeoStores; import org.neo4j.kernel.impl.store.RecordCursors; +import org.neo4j.kernel.impl.store.RecordStore; +import org.neo4j.kernel.impl.store.RelationshipStore; import org.neo4j.kernel.impl.store.record.NodeRecord; +import org.neo4j.kernel.impl.store.record.Record; +import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; +import org.neo4j.storageengine.api.DegreeItem; +import org.neo4j.storageengine.api.Direction; +import org.neo4j.storageengine.api.LabelItem; +import org.neo4j.storageengine.api.NodeItem; +import org.neo4j.storageengine.api.PropertyItem; +import org.neo4j.storageengine.api.RelationshipItem; +import org.neo4j.storageengine.api.RelationshipTypeItem; +import static java.util.function.Function.identity; +import static org.neo4j.kernel.impl.api.store.DegreeCounter.countRelationshipsInGroup; +import static org.neo4j.kernel.impl.locking.LockService.NO_LOCK_SERVICE; import static org.neo4j.kernel.impl.store.record.RecordLoad.CHECK; /** - * Cursor for a single node. + * Base cursor for nodes. */ -public class StoreSingleNodeCursor extends StoreAbstractNodeCursor +public class StoreSingleNodeCursor extends EntityItemHelper implements Cursor, NodeItem { - private long nodeId = StatementConstants.NO_SUCH_NODE; + private final NodeRecord nodeRecord; + private final RelationshipStore relationshipStore; + private final RecordStore relationshipGroupStore; private final Consumer instanceCache; - StoreSingleNodeCursor( NodeRecord nodeRecord, - NeoStores neoStores, - StoreStatement storeStatement, - Consumer instanceCache, - RecordCursors cursors, - LockService lockService ) + private final LockService lockService; + private final RecordCursors recordCursors; + private final NodeExploringCursors cursors; + + private long nodeId = StatementConstants.NO_SUCH_NODE; + + StoreSingleNodeCursor( NodeRecord nodeRecord, NeoStores neoStores, Consumer instanceCache, + RecordCursors recordCursors, LockService lockService ) { - super( nodeRecord, neoStores, storeStatement, cursors, lockService ); + this.nodeRecord = nodeRecord; + this.recordCursors = recordCursors; + this.relationshipStore = neoStores.getRelationshipStore(); + this.relationshipGroupStore = neoStores.getRelationshipGroupStore(); + this.lockService = lockService; this.instanceCache = instanceCache; + this.cursors = + new NodeExploringCursors( recordCursors, lockService, relationshipStore, relationshipGroupStore ); } public StoreSingleNodeCursor init( long nodeId ) @@ -54,6 +84,12 @@ public StoreSingleNodeCursor init( long nodeId ) return this; } + @Override + public NodeItem get() + { + return this; + } + @Override public boolean next() { @@ -61,7 +97,7 @@ public boolean next() { try { - return cursors.node().next( nodeId, nodeRecord, CHECK ); + return recordCursors.node().next( nodeId, nodeRecord, CHECK ); } finally { @@ -77,4 +113,170 @@ public void close() { instanceCache.accept( this ); } + + @Override + public long id() + { + return nodeRecord.getId(); + } + + @Override + public Cursor labels() + { + return cursors.labels( nodeRecord ); + } + + @Override + public Cursor label( int labelId ) + { + return cursors.label( nodeRecord, labelId ); + } + + @Override + public boolean hasLabel( int labelId ) + { + return label( labelId ).exists(); + } + + private Lock shortLivedReadLock() + { + Lock lock = lockService.acquireNodeLock( nodeRecord.getId(), LockService.LockType.READ_LOCK ); + if ( lockService != NO_LOCK_SERVICE ) + { + boolean success = false; + try + { + // It's safer to re-read the node record here, specifically nextProp, after acquiring the lock + if ( !recordCursors.node().next( nodeRecord.getId(), nodeRecord, CHECK ) ) + { + // So it looks like the node has been deleted. The current behavior of NodeStore#loadRecord + // is to only set the inUse field on loading an unused record. This should (and will) + // change to be more of a centralized behavior by the stores. Anyway, setting this pointer + // to the primitive equivalent of null the property cursor will just look empty from the + // outside and the releasing of the lock will be done as usual. + nodeRecord.setNextProp( Record.NO_NEXT_PROPERTY.intValue() ); + } + success = true; + } + finally + { + if ( !success ) + { + lock.release(); + } + } + } + return lock; + } + + @Override + public Cursor properties() + { + return cursors.properties( nodeRecord.getNextProp(), shortLivedReadLock() ); + } + + @Override + public Cursor property( int propertyKeyId ) + { + return cursors.property( nodeRecord.getNextProp(), propertyKeyId, shortLivedReadLock() ); + } + + @Override + public Cursor relationships( Direction direction ) + { + return cursors.relationships( nodeRecord.isDense(), nodeRecord.getNextRel(), nodeRecord.getId(), direction ); + } + + @Override + public Cursor relationships( Direction direction, int... relTypes ) + { + return cursors.relationships( nodeRecord.isDense(), nodeRecord.getNextRel(), nodeRecord.getId(), direction, + relTypes ); + } + + @Override + public Cursor relationshipTypes() + { + if ( nodeRecord.isDense() ) + { + long groupId = nodeRecord.getNextRel(); + return new RelationshipTypeDenseCursor( groupId, relationshipGroupStore.newRecord(), recordCursors ); + } + else + { + return new RelationshipTypeCursor( relationships( Direction.BOTH ) ); + } + } + + @Override + public int degree( Direction direction ) + { + if ( nodeRecord.isDense() ) + { + return countRelationshipsInGroup( nodeRecord.getNextRel(), direction, null, nodeRecord, + relationshipStore.newRecord(), relationshipGroupStore.newRecord(), recordCursors ); + } + else + { + return relationships( direction ).count(); + } + } + + @Override + public int degree( Direction direction, int relType ) + { + if ( nodeRecord.isDense() ) + { + return countRelationshipsInGroup( nodeRecord.getNextRel(), direction, relType, nodeRecord, + relationshipStore.newRecord(), relationshipGroupStore.newRecord(), recordCursors ); + } + else + { + return relationships( direction, relType ).count(); + } + } + + @Override + public Cursor degrees() + { + return nodeRecord.isDense() ? cursors.degrees( nodeRecord.getNextRel(), nodeRecord, recordCursors ) + : cursors.degrees( buildDegreeMap() ); + } + + private PrimitiveIntObjectMap buildDegreeMap() + { + return relationships( Direction.BOTH ) + .mapReduce( Primitive.intObjectMap( 5 ), identity(), ( rel, currentDegrees ) -> + { + int[] byType = currentDegrees.get( rel.type() ); + if ( byType == null ) + { + currentDegrees.put( rel.type(), byType = new int[3] ); + } + byType[directionOf( nodeRecord.getId(), rel.id(), rel.startNode(), rel.endNode() ).ordinal()]++; + return currentDegrees; + } ); + } + + @Override + public boolean isDense() + { + return nodeRecord.isDense(); + } + + private Direction directionOf( long nodeId, long relationshipId, long startNode, long endNode ) + { + if ( startNode == nodeId ) + { + return endNode == nodeId ? Direction.BOTH : Direction.OUTGOING; + } + if ( endNode == nodeId ) + { + return Direction.INCOMING; + } + throw new InvalidRecordException( + "Node " + nodeId + " neither start nor end node of relationship " + relationshipId + + " with startNode:" + startNode + " and endNode:" + endNode ); + } + } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreStatement.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreStatement.java index be0dc96b62f53..50e8d9bb2c133 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreStatement.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StoreStatement.java @@ -78,7 +78,7 @@ public StoreStatement( NeoStores neoStores, Supplier indexRe @Override protected StoreSingleNodeCursor create() { - return new StoreSingleNodeCursor( nodeStore.newRecord(), neoStores, StoreStatement.this, this, + return new StoreSingleNodeCursor( nodeStore.newRecord(), neoStores, this, recordCursors, lockService ); } }; 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 e826291059f00..4d4fee31d947e 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 @@ -53,7 +53,6 @@ import static org.neo4j.io.pagecache.PagedFile.PF_READ_AHEAD; import static org.neo4j.io.pagecache.PagedFile.PF_SHARED_READ_LOCK; import static org.neo4j.io.pagecache.PagedFile.PF_SHARED_WRITE_LOCK; -import static org.neo4j.kernel.impl.store.record.Record.NULL_REFERENCE; import static org.neo4j.kernel.impl.store.record.RecordLoad.CHECK; import static org.neo4j.kernel.impl.store.record.RecordLoad.NORMAL; @@ -63,11 +62,11 @@ public abstract class CommonAbstractStore implements RecordStore, AutoCloseable { - public static final String UNKNOWN_VERSION = "Unknown"; + static final String UNKNOWN_VERSION = "Unknown"; protected final Config configuration; protected final PageCache pageCache; - protected final File storageFileName; + final File storageFileName; protected final IdType idType; protected final IdGeneratorFactory idGeneratorFactory; protected final Log log; @@ -261,7 +260,7 @@ protected void initialiseNewStoreFile( PagedFile file ) throws IOException idGeneratorFactory.create( idFileName, getNumberOfReservedLowIds(), false ); } - protected void createHeaderRecord( PageCursor cursor ) throws IOException + private void createHeaderRecord( PageCursor cursor ) throws IOException { int offset = cursor.getOffset(); storeHeaderFormat.writeHeader( cursor ); @@ -531,7 +530,7 @@ private void checkIdScanCursorBounds( PageCursor cursor ) /** * Marks this store as "not ok". */ - protected void setStoreNotOk( Throwable cause ) + void setStoreNotOk( Throwable cause ) { storeOk = false; causeOfStoreNotOk = cause; @@ -543,7 +542,7 @@ protected void setStoreNotOk( Throwable cause ) * * @return True if this store is ok */ - protected boolean getStoreOk() + boolean getStoreOk() { return storeOk; } @@ -551,7 +550,7 @@ protected boolean getStoreOk() /** * Throws cause of not being OK if {@link #getStoreOk()} returns {@code false}. */ - protected void checkStoreOk() + void checkStoreOk() { if ( !storeOk ) { @@ -632,7 +631,7 @@ public void setHighId( long highId ) * To remove all negations from the above statement: Only call this method if store is in need of * recovery and recovery has been performed. */ - public void makeStoreOk() + void makeStoreOk() { if ( !storeOk ) { @@ -666,7 +665,7 @@ private File getIdFileName() * map their own temporary PagedFile for the store file, and do their file IO through that, * if they need to access the data in the store file. */ - protected void openIdGenerator() + void openIdGenerator() { idGenerator = idGeneratorFactory.open( getIdFileName(), getIdType(), scanForHighId(), recordFormat.getMaxId() ); } @@ -810,13 +809,13 @@ protected boolean isRecordReserved( PageCursor cursor ) return false; } - protected void createIdGenerator( File fileName ) + private void createIdGenerator( File fileName ) { idGeneratorFactory.create( fileName, 0, false ); } /** Closed the {@link IdGenerator} used by this store */ - protected void closeIdGenerator() + void closeIdGenerator() { if ( idGenerator != null ) { @@ -842,7 +841,7 @@ public void flush() * * @throws IllegalStateException if the store is closed */ - protected void assertNotClosed() + void assertNotClosed() { if ( storeFile == null ) { @@ -946,12 +945,12 @@ public IdType getIdType() return idType; } - public void logVersions( Logger logger ) + void logVersions( Logger logger ) { logger.log( " " + getTypeDescriptor() + " " + storeVersion ); } - public void logIdUsage( Logger logger ) + void logIdUsage( Logger logger ) { logger.log( String.format( " %s: used=%s high=%s", getTypeDescriptor(), getNumberOfIdsInUse(), getHighestPossibleIdInUse() ) ); @@ -967,7 +966,7 @@ public void logIdUsage( Logger logger ) * {@link #logVersions(Logger)} * For a good samaritan to pick up later. */ - public void visitStore( Visitor,RuntimeException> visitor ) + void visitStore( Visitor,RuntimeException> visitor ) { visitor.visit( this ); } @@ -1018,15 +1017,9 @@ public RECORD newRecord() @Override public RECORD getRecord( long id, RECORD record, RecordLoad mode ) { - // 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 - // on that record, so it's to ensure it isn't forgotten. - record.setId( id ); - long pageId = pageIdForRecord( id ); - int offset = offsetForId( id ); - try ( PageCursor cursor = storeFile.io( pageId, PF_SHARED_READ_LOCK ) ) + try ( PageCursor cursor = storeFile.io( getNumberOfReservedLowIds(), PF_SHARED_READ_LOCK ) ) { - readIntoRecord( id, record, mode, pageId, offset, cursor ); + readIntoRecord( id, record, mode, cursor ); return record; } catch ( IOException e ) @@ -1035,9 +1028,14 @@ public RECORD getRecord( long id, RECORD record, RecordLoad mode ) } } - private void readIntoRecord( long id, RECORD record, RecordLoad mode, long pageId, int offset, PageCursor cursor ) - throws IOException + 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 + // on that record, so it's to ensure it isn't forgotten. + record.setId( id ); + long pageId = pageIdForRecord( id ); + int offset = offsetForId( id ); if ( cursor.next( pageId ) ) { // There is a page in the store that covers this record, go read it @@ -1128,109 +1126,17 @@ public Collection getRecords( long firstId, RecordLoad mode ) @Override public RecordCursor newRecordCursor( final RECORD record ) { - return new RecordCursor() - { - private long currentId; - private RecordLoad mode; - private PageCursor pageCursor; - - @Override - public boolean next() - { - try - { - return next( currentId, record, mode ); - } - finally - { - // This will get the next reference: - // inUse ==> actual next reference - // !inUse && mode == CHECK ==> NULL - // !inUse && mode == NORMAL ==> NULL (+InvalidRecordException thrown in try) - // !inUse && mode == FORCE ==> actual next reference - currentId = getNextRecordReference( record ); - } - } - - @Override - public boolean next( long id ) - { - return next( id, record, mode ); - } - - @Override - public boolean next( long id, RECORD record, RecordLoad mode ) - { - assert pageCursor != null : "Not initialized"; - if ( NULL_REFERENCE.is( id ) ) - { - record.clear(); - record.setId( NULL_REFERENCE.intValue() ); - return false; - } - - try - { - record.setId( id ); - long pageId = pageIdForRecord( id ); - int offset = offsetForId( id ); - readIntoRecord( currentId, record, mode, pageId, offset, pageCursor ); - return record.inUse(); - } - catch ( IOException e ) - { - throw new UnderlyingStorageException( e ); - } - } - - @Override - public void placeAt( long id, RecordLoad mode ) - { - this.currentId = id; - this.mode = mode; - } - - @Override - public void close() - { - assert pageCursor != null; - this.pageCursor.close(); - this.pageCursor = null; - } - - @Override - public RECORD get() - { - return record; - } - - @Override - public RecordCursor acquire( long id, RecordLoad mode ) - { - assert this.pageCursor == null; - this.currentId = id; - this.mode = mode; - try - { - this.pageCursor = storeFile.io( pageIdForRecord( id ), PF_SHARED_READ_LOCK ); - } - catch ( IOException e ) - { - throw new UnderlyingStorageException( e ); - } - return this; - } - }; + return new StoreRecordCursor<>( record, this ); } - protected void verifyAfterNotRead( RECORD record, RecordLoad mode ) + private void verifyAfterNotRead( RECORD record, RecordLoad mode ) { record.clear(); mode.verify( record ); } - protected final void checkForDecodingErrors( PageCursor cursor, long recordId, RecordLoad mode ) + final void checkForDecodingErrors( PageCursor cursor, long recordId, RecordLoad mode ) { if ( mode.checkForOutOfBounds( cursor ) ) { @@ -1249,8 +1155,8 @@ private void throwOutOfBoundsException( long recordId ) record, pageId, offset, recordSize, storeFile.pageSize(), storageFileName.getAbsolutePath() ) ); } - protected static String buildOutOfBoundsExceptionMessage( AbstractBaseRecord record, long pageId, int offset, - int recordSize, int pageSize, String filename ) + static String buildOutOfBoundsExceptionMessage( AbstractBaseRecord record, long pageId, int offset, int recordSize, + int pageSize, String filename ) { return "Access to record " + record + " went out of bounds of the page. The record size is " + recordSize + " bytes, and the access was at offset " + offset + " bytes into page " + @@ -1258,7 +1164,7 @@ protected static String buildOutOfBoundsExceptionMessage( AbstractBaseRecord rec "The mapped store file in question is " + filename; } - protected final void verifyAfterReading( RECORD record, RecordLoad mode ) + private void verifyAfterReading( RECORD record, RecordLoad mode ) { if ( !mode.verify( record ) ) { @@ -1266,7 +1172,7 @@ protected final void verifyAfterReading( RECORD record, RecordLoad mode ) } } - protected final void prepareForReading( PageCursor cursor, int offset, RECORD record ) + private void prepareForReading( PageCursor cursor, int offset, RECORD record ) { // Mark this record as unused. This to simplify implementations of readRecord. // readRecord can behave differently depending on RecordLoad argument and so it may be that @@ -1297,7 +1203,6 @@ public int getStoreHeaderInt() public abstract static class Configuration { - public static final Setting rebuild_idgenerators_fast = - GraphDatabaseSettings.rebuild_idgenerators_fast; + static final Setting rebuild_idgenerators_fast = GraphDatabaseSettings.rebuild_idgenerators_fast; } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/RecordCursors.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/RecordCursors.java index 7c6272faf357a..f1aa78647d6c1 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/RecordCursors.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/RecordCursors.java @@ -45,23 +45,18 @@ public class RecordCursors implements AutoCloseable public RecordCursors( NeoStores neoStores ) { - this( neoStores, NORMAL ); + node = newCursor( neoStores.getNodeStore() ); + relationship = newCursor( neoStores.getRelationshipStore() ); + relationshipGroup = newCursor( neoStores.getRelationshipGroupStore() ); + property = newCursor( neoStores.getPropertyStore() ); + propertyString = newCursor( neoStores.getPropertyStore().getStringStore() ); + propertyArray = newCursor( neoStores.getPropertyStore().getArrayStore() ); + label = newCursor( neoStores.getNodeStore().getDynamicLabelStore() ); } - public RecordCursors( NeoStores neoStores, RecordLoad mode ) + private static RecordCursor newCursor( RecordStore store ) { - node = newCursor( neoStores.getNodeStore(), mode ); - relationship = newCursor( neoStores.getRelationshipStore(), mode ); - relationshipGroup = newCursor( neoStores.getRelationshipGroupStore(), mode ); - property = newCursor( neoStores.getPropertyStore(), mode ); - propertyString = newCursor( neoStores.getPropertyStore().getStringStore(), mode ); - propertyArray = newCursor( neoStores.getPropertyStore().getArrayStore(), mode ); - label = newCursor( neoStores.getNodeStore().getDynamicLabelStore(), mode ); - } - - private static RecordCursor newCursor( RecordStore store, RecordLoad mode ) - { - return store.newRecordCursor( store.newRecord() ).acquire( store.getNumberOfReservedLowIds(), mode ); + return store.newRecordCursor( store.newRecord() ).acquire( store.getNumberOfReservedLowIds(), NORMAL ); } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/StoreRecordCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/StoreRecordCursor.java new file mode 100644 index 0000000000000..0688cf9d2e8f1 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/StoreRecordCursor.java @@ -0,0 +1,128 @@ +/* + * 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.store; + +import java.io.IOException; + +import org.neo4j.io.pagecache.PageCursor; +import org.neo4j.kernel.impl.store.record.AbstractBaseRecord; +import org.neo4j.kernel.impl.store.record.RecordLoad; + +import static org.neo4j.io.pagecache.PagedFile.PF_SHARED_READ_LOCK; +import static org.neo4j.kernel.impl.store.record.Record.NULL_REFERENCE; + +class StoreRecordCursor implements RecordCursor +{ + private final RECORD record; + private CommonAbstractStore store; + private long currentId; + private RecordLoad mode; + private PageCursor pageCursor; + + StoreRecordCursor( RECORD record, CommonAbstractStore store ) + { + this.record = record; + this.store = store; + } + + @Override + public boolean next() + { + try + { + return next( currentId, record, mode ); + } + finally + { + // This will get the next reference: + // inUse ==> actual next reference + // !inUse && mode == CHECK ==> NULL + // !inUse && mode == NORMAL ==> NULL (+InvalidRecordException thrown in try) + // !inUse && mode == FORCE ==> actual next reference + currentId = store.getNextRecordReference( record ); + } + } + + @Override + public boolean next( long id ) + { + return next( id, record, mode ); + } + + @Override + public boolean next( long id, RECORD record, RecordLoad mode ) + { + assert pageCursor != null : "Not initialized"; + if ( NULL_REFERENCE.is( id ) ) + { + record.clear(); + record.setId( NULL_REFERENCE.intValue() ); + return false; + } + + try + { + store.readIntoRecord( id, record, mode, pageCursor ); + return record.inUse(); + } + catch ( IOException e ) + { + throw new UnderlyingStorageException( e ); + } + } + + @Override + public void placeAt( long id, RecordLoad mode ) + { + this.currentId = id; + this.mode = mode; + } + + @Override + public void close() + { + assert pageCursor != null; + this.pageCursor.close(); + this.pageCursor = null; + } + + @Override + public RECORD get() + { + return record; + } + + @Override + public RecordCursor acquire( long id, RecordLoad mode ) + { + assert this.pageCursor == null; + this.currentId = id; + this.mode = mode; + try + { + this.pageCursor = store.storeFile.io( store.pageIdForRecord( id ), PF_SHARED_READ_LOCK ); + } + catch ( IOException e ) + { + throw new UnderlyingStorageException( e ); + } + return this; + } +} diff --git a/community/kernel/src/main/java/org/neo4j/storageengine/api/NodeItem.java b/community/kernel/src/main/java/org/neo4j/storageengine/api/NodeItem.java index 5452629625132..3078bc7e259b6 100644 --- a/community/kernel/src/main/java/org/neo4j/storageengine/api/NodeItem.java +++ b/community/kernel/src/main/java/org/neo4j/storageengine/api/NodeItem.java @@ -32,16 +32,6 @@ public interface NodeItem extends EntityItem { - /** - * Convenience function for extracting a label id from a {@link LabelItem}. - */ - ToIntFunction GET_LABEL = IntSupplier::getAsInt; - - /** - * Convenience function for extracting a relationship type id from a {@link IntSupplier}. - */ - ToIntFunction GET_RELATIONSHIP_TYPE = IntSupplier::getAsInt; - /** * @return label cursor for current node * @throws IllegalStateException if no current node is selected @@ -68,10 +58,10 @@ public interface NodeItem Cursor relationships( Direction direction ); /** - * @return relationship types, wrapped in {@link IntSupplier} instances for relationships attached to this node. + * @return relationship type cursor for relationships attached to this node. * @throws IllegalStateException if no current node is selected */ - Cursor relationshipTypes(); + Cursor relationshipTypes(); /** * Returns degree, e.g. number of relationships for this node. @@ -109,27 +99,4 @@ public interface NodeItem * @return whether or not this node has the given label. */ boolean hasLabel( int labelId ); - - /** - * @return label ids attached to this node. - */ - PrimitiveIntIterator getLabels(); - - /** - * @param direction {@link Direction} to filter on. - * @param typeIds relationship type ids to filter on. - * @return relationship ids for the given direction and relationship types. - */ - RelationshipIterator getRelationships( Direction direction, int[] typeIds ); - - /** - * @param direction {@link Direction} to filter on. - * @return relationship ids for the given direction. - */ - RelationshipIterator getRelationships( Direction direction ); - - /** - * @return relationship type ids for all relationships attached to this node. - */ - PrimitiveIntIterator getRelationshipTypes(); } diff --git a/community/primitive-collections/src/main/java/org/neo4j/cursor/GenericCursor.java b/community/kernel/src/main/java/org/neo4j/storageengine/api/RelationshipTypeItem.java similarity index 55% rename from community/primitive-collections/src/main/java/org/neo4j/cursor/GenericCursor.java rename to community/kernel/src/main/java/org/neo4j/storageengine/api/RelationshipTypeItem.java index ec6134648f967..cc76d6e5177c2 100644 --- a/community/primitive-collections/src/main/java/org/neo4j/cursor/GenericCursor.java +++ b/community/kernel/src/main/java/org/neo4j/storageengine/api/RelationshipTypeItem.java @@ -17,36 +17,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.cursor; +package org.neo4j.storageengine.api; -/** - * Generic base class for cursor where clients - * can access the current state through the get method. - *

- * Subclasses must implement the {@link #next()} method and - * set the current field to the next item. - * - * @param the type of instances being iterated - */ -public abstract class GenericCursor - implements Cursor -{ - protected T current; +import java.util.function.IntSupplier; - @Override - public T get() - { - if ( current == null ) - { - throw new IllegalStateException(); - } - - return current; - } - - @Override - public void close() - { - current = null; - } +public interface RelationshipTypeItem extends IntSupplier +{ } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/LockingStatementOperationsTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/LockingStatementOperationsTest.java index c136f333c9e84..6dc3472209862 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/LockingStatementOperationsTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/LockingStatementOperationsTest.java @@ -40,14 +40,13 @@ import org.neo4j.kernel.api.txstate.LegacyIndexTransactionState; import org.neo4j.kernel.api.txstate.TransactionState; import org.neo4j.kernel.api.txstate.TxStateHolder; +import org.neo4j.kernel.impl.api.TwoPhaseNodeForRelationshipLockingTest.RelationshipData; import org.neo4j.kernel.impl.api.operations.EntityReadOperations; import org.neo4j.kernel.impl.api.operations.EntityWriteOperations; import org.neo4j.kernel.impl.api.operations.SchemaReadOperations; import org.neo4j.kernel.impl.api.operations.SchemaStateOperations; import org.neo4j.kernel.impl.api.operations.SchemaWriteOperations; -import org.neo4j.kernel.impl.api.state.StubCursors; import org.neo4j.kernel.impl.api.state.TxState; -import org.neo4j.kernel.impl.api.store.CursorRelationshipIterator; import org.neo4j.kernel.impl.api.store.RelationshipIterator; import org.neo4j.kernel.impl.api.store.StoreSingleNodeCursor; import org.neo4j.kernel.impl.factory.CanWrite; @@ -56,16 +55,13 @@ import org.neo4j.kernel.impl.locking.ResourceTypes; import org.neo4j.kernel.impl.locking.SimpleStatementLocks; import org.neo4j.kernel.impl.proc.Procedures; -import org.neo4j.kernel.impl.util.Cursors; import org.neo4j.storageengine.api.Direction; import org.neo4j.storageengine.api.NodeItem; -import org.neo4j.storageengine.api.RelationshipItem; import org.neo4j.storageengine.api.StorageStatement; import static org.junit.Assert.assertSame; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -73,6 +69,7 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; +import static org.neo4j.kernel.impl.api.TwoPhaseNodeForRelationshipLockingTest.returnRelationships; import static org.neo4j.kernel.impl.locking.ResourceTypes.schemaResource; public class LockingStatementOperationsTest @@ -496,12 +493,7 @@ public void shouldNotAcquireEntityWriteLockBeforeSettingPropertyOnJustCreatedRel public void detachDeleteNodeWithoutRelationshipsExclusivelyLockNode() throws KernelException { long nodeId = 1L; - - NodeItem nodeItem = mock( NodeItem.class ); - when( nodeItem.getRelationships( Direction.BOTH ) ).thenReturn( RelationshipIterator.EMPTY ); - StoreSingleNodeCursor nodeCursor = mock( StoreSingleNodeCursor.class ); - when( nodeCursor.get() ).thenReturn( nodeItem ); - when( entityReadOps.nodeCursorById( state, nodeId ) ).thenReturn( nodeCursor ); + returnRelationships( entityReadOps, state, nodeId, false ); lockingOps.nodeDetachDelete( state, nodeId ); @@ -513,33 +505,17 @@ public void detachDeleteNodeWithoutRelationshipsExclusivelyLockNode() throws Ker @Test public void detachDeleteNodeExclusivelyLockNodes() throws KernelException { - long startNodeId = 1L; - long endNodeId = 2L; - - RelationshipItem relationshipItem = StubCursors.asRelationship( 1L, 0, startNodeId, endNodeId, null ); - CursorRelationshipIterator relationshipIterator = - new CursorRelationshipIterator( Cursors.cursor( relationshipItem ) ); - - NodeItem nodeItem = mock( NodeItem.class ); - when( nodeItem.getRelationships( Direction.BOTH ) ).thenReturn( relationshipIterator ); - StoreSingleNodeCursor nodeCursor = mock( StoreSingleNodeCursor.class ); - when( nodeCursor.get() ).thenReturn( nodeItem ); - when( entityReadOps.nodeCursorById( state, startNodeId ) ).thenReturn( nodeCursor ); - doAnswer( invocation -> - { - RelationshipVisitor visitor = invocation.getArgumentAt( 2, RelationshipVisitor.class ); - visitor.visit( relationshipItem.id(), relationshipItem.type(), relationshipItem.startNode(), - relationshipItem.endNode() ); - return null; - } ).when( entityReadOps ).relationshipVisit( eq(state), anyLong(), any() ); + long nodeId = 1L; + RelationshipData relationship = new RelationshipData( 1, nodeId, 2L ); + returnRelationships( entityReadOps, state, nodeId, false, relationship ); - lockingOps.nodeDetachDelete( state, startNodeId ); + lockingOps.nodeDetachDelete( state, nodeId ); - order.verify( locks ).acquireExclusive( LockTracer.NONE, ResourceTypes.NODE, startNodeId ); - order.verify( locks ).acquireExclusive( LockTracer.NONE, ResourceTypes.NODE, endNodeId ); - order.verify( locks, times( 0 ) ).releaseExclusive( ResourceTypes.NODE, startNodeId ); - order.verify( locks, times( 0 ) ).releaseExclusive( ResourceTypes.NODE, endNodeId ); - order.verify( entityWriteOps ).nodeDetachDelete( state, startNodeId ); + order.verify( locks ).acquireExclusive( LockTracer.NONE, ResourceTypes.NODE, relationship.startNodeId ); + order.verify( locks ).acquireExclusive( LockTracer.NONE, ResourceTypes.NODE, relationship.endNodeId ); + order.verify( locks, times( 0 ) ).releaseExclusive( ResourceTypes.NODE, relationship.startNodeId ); + order.verify( locks, times( 0 ) ).releaseExclusive( ResourceTypes.NODE, relationship.endNodeId ); + order.verify( entityWriteOps ).nodeDetachDelete( state, nodeId ); } private static class SimpleTxStateHolder implements TxStateHolder diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/TwoPhaseNodeForRelationshipLockingTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/TwoPhaseNodeForRelationshipLockingTest.java index 67711cb7f8c4b..140655ca85058 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/TwoPhaseNodeForRelationshipLockingTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/TwoPhaseNodeForRelationshipLockingTest.java @@ -19,34 +19,31 @@ */ package org.neo4j.kernel.impl.api; -import java.util.HashSet; -import java.util.NoSuchElementException; -import java.util.Set; - -import org.apache.commons.lang3.NotImplementedException; import org.junit.Test; import org.mockito.InOrder; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.util.HashSet; +import java.util.NoSuchElementException; +import java.util.Set; + import org.neo4j.cursor.Cursor; import org.neo4j.function.ThrowingConsumer; import org.neo4j.kernel.api.exceptions.EntityNotFoundException; import org.neo4j.kernel.api.exceptions.KernelException; import org.neo4j.kernel.impl.api.operations.EntityReadOperations; -import org.neo4j.kernel.impl.api.store.RelationshipIterator; import org.neo4j.kernel.impl.locking.LockTracer; import org.neo4j.kernel.impl.locking.Locks; import org.neo4j.kernel.impl.locking.ResourceTypes; import org.neo4j.kernel.impl.locking.SimpleStatementLocks; import org.neo4j.storageengine.api.Direction; +import org.neo4j.storageengine.api.EntityType; import org.neo4j.storageengine.api.NodeItem; +import org.neo4j.storageengine.api.RelationshipItem; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -73,10 +70,10 @@ public void shouldLockNodesInOrderAndConsumeTheRelationships() throws Throwable Collector collector = new Collector(); TwoPhaseNodeForRelationshipLocking locking = new TwoPhaseNodeForRelationshipLocking( ops, collector ); - returnRelationships( nodeId, false, 21L, 22L, 23L ); - returnNodesForRelationship( 21L, nodeId, 43L ); - returnNodesForRelationship( 22L, 40L, nodeId ); - returnNodesForRelationship( 23L, nodeId, 41L ); + RelationshipData relationship1 = new RelationshipData( 21L, nodeId, 43L ); + RelationshipData relationship2 = new RelationshipData( 22L, 40L, nodeId ); + RelationshipData relationship3 = new RelationshipData( 23L, nodeId, 41L ); + returnRelationships( ops, state, nodeId, false, relationship1, relationship2, relationship3 ); InOrder inOrder = inOrder( locks ); @@ -92,16 +89,17 @@ public void shouldLockNodesInOrderAndConsumeTheRelationships() throws Throwable } @Test - public void shouldLockNodesInOrderAndConsumeTheRelationshipsAndRetryIfTheNewRelationshipsAreCreated() throws Throwable + public void shouldLockNodesInOrderAndConsumeTheRelationshipsAndRetryIfTheNewRelationshipsAreCreated() + throws Throwable { // given Collector collector = new Collector(); TwoPhaseNodeForRelationshipLocking locking = new TwoPhaseNodeForRelationshipLocking( ops, collector ); - returnRelationships( nodeId, true, 21L, 22L, 23L ); - returnNodesForRelationship( 21L, nodeId, 43L ); - returnNodesForRelationship( 22L, 40L, nodeId ); - returnNodesForRelationship( 23L, nodeId, 41L ); + RelationshipData relationship1 = new RelationshipData( 21L, nodeId, 43L ); + RelationshipData relationship2 = new RelationshipData( 22L, 40L, nodeId ); + RelationshipData relationship3 = new RelationshipData( 23L, nodeId, 41L ); + returnRelationships( ops, state, nodeId, true, relationship1, relationship2, relationship3 ); InOrder inOrder = inOrder( locks ); @@ -129,7 +127,7 @@ public void lockNodeWithoutRelationships() throws Exception { Collector collector = new Collector(); TwoPhaseNodeForRelationshipLocking locking = new TwoPhaseNodeForRelationshipLocking( ops, collector ); - returnRelationships( nodeId, false ); + returnRelationships( ops, state, nodeId, false ); locking.lockAllNodesAndConsumeRelationships( nodeId, state ); @@ -137,65 +135,69 @@ public void lockNodeWithoutRelationships() throws Exception verifyNoMoreInteractions( locks ); } - private void returnNodesForRelationship( final long relId, final long startNodeId, final long endNodeId ) - throws Exception + public static class RelationshipData { - doAnswer( new Answer() + public final long relId; + public final long startNodeId; + public final long endNodeId; + + RelationshipData( long relId, long startNodeId, long endNodeId ) { - @Override - public Void answer( InvocationOnMock invocation ) throws Throwable - { - @SuppressWarnings( "unchecked" ) - RelationshipVisitor visitor = - (RelationshipVisitor) invocation.getArguments()[2]; - visitor.visit( relId, 6, startNodeId, endNodeId ); - return null; - } - } ).when( ops ).relationshipVisit( eq( state ), eq( relId ), any( RelationshipVisitor.class ) ); + this.relId = relId; + this.startNodeId = startNodeId; + this.endNodeId = endNodeId; + } + + RelationshipItem asRelationshipItem() + { + RelationshipItem rel = mock( RelationshipItem.class ); + when( rel.id() ).thenReturn( relId ); + when( rel.startNode() ).thenReturn( startNodeId ); + when( rel.endNode() ).thenReturn( endNodeId ); + return rel; + } } - private void returnRelationships( long nodeId, final boolean skipFirst, final long... relIds ) - throws EntityNotFoundException + static void returnRelationships( EntityReadOperations ops, KernelStatement state, long nodeId, + final boolean skipFirst, final RelationshipData... relIds ) throws EntityNotFoundException { - //noinspection unchecked - Cursor cursor = mock( Cursor.class ); - when( ops.nodeCursorById( state, nodeId ) ).thenReturn( cursor ); NodeItem nodeItem = mock( NodeItem.class ); - when( cursor.get() ).thenReturn( nodeItem ); - when( nodeItem.getRelationships( Direction.BOTH ) ).thenAnswer( new Answer() + when( nodeItem.relationships( Direction.BOTH ) ).thenAnswer( new Answer>() { private boolean first = skipFirst; @Override - public RelationshipIterator answer( InvocationOnMock invocation ) throws Throwable + public Cursor answer( InvocationOnMock invocation ) throws Throwable { try { - return new RelationshipIterator() + return new Cursor() { private int i = first ? 1 : 0; + private RelationshipData relationshipData = null; @Override - public boolean relationshipVisit( long relationshipId, - RelationshipVisitor visitor ) + public boolean next() { - throw new NotImplementedException( "don't call this!" ); + boolean next = i < relIds.length; + relationshipData = next ? relIds[i++] : null; + return next; } @Override - public boolean hasNext() + public RelationshipItem get() { - return i < relIds.length; + if ( relationshipData == null ) + { + throw new NoSuchElementException(); + } + + return relationshipData.asRelationshipItem(); } @Override - public long next() + public void close() { - if ( !hasNext() ) - { - throw new NoSuchElementException(); - } - return relIds[i++]; } }; } @@ -204,7 +206,42 @@ public long next() first = false; } } - }); + } ); + + when( ops.nodeCursorById( state, nodeId ) ).thenAnswer( invocationOnMock -> + { + Cursor cursor = new Cursor() + { + private int i = 0; + + @Override + public boolean next() + { + return i++ == 0; + } + + @Override + public NodeItem get() + { + if ( i != 1 ) + { + throw new NoSuchElementException(); + } + return nodeItem; + } + + @Override + public void close() + { + + } + }; + if ( !cursor.next() ) + { + throw new EntityNotFoundException( EntityType.NODE, nodeId ); + } + return cursor; + } ); } private static class Collector implements ThrowingConsumer diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/cursor/TxSingleNodeCursorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/cursor/TxSingleNodeCursorTest.java new file mode 100644 index 0000000000000..8a93e17957f5e --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/cursor/TxSingleNodeCursorTest.java @@ -0,0 +1,55 @@ +/* + * 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.api.cursor; + +import org.junit.Test; + +import org.neo4j.kernel.api.txstate.TransactionState; +import org.neo4j.kernel.impl.util.Cursors; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TxSingleNodeCursorTest +{ + + private final TransactionState state = mock( TransactionState.class ); + private TxSingleNodeCursor cursor = new TxSingleNodeCursor( state, ( l ) -> + { + } ); + + @Test + public void shouldNotLoopForeverWhenNodesAreAddedToTheTxState() throws Exception + { + // given + int nodeId = 42; + when( state.nodeIsDeletedInThisTx( nodeId ) ).thenReturn( false ); + when( state.nodeIsAddedInThisTx( nodeId ) ).thenReturn( true ); + + // when + cursor.init( Cursors.empty(), nodeId ); + + // then + assertTrue( cursor.next() ); + assertFalse( cursor.next() ); + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/LabelTransactionStateTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/LabelTransactionStateTest.java index face7d8b6b964..a810cfa573e4e 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/LabelTransactionStateTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/LabelTransactionStateTest.java @@ -26,11 +26,14 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.function.IntSupplier; +import org.neo4j.collection.primitive.Primitive; import org.neo4j.collection.primitive.PrimitiveIntCollections; +import org.neo4j.collection.primitive.PrimitiveIntSet; import org.neo4j.collection.primitive.PrimitiveLongCollections; -import org.neo4j.cursor.Cursor; import org.neo4j.kernel.api.exceptions.EntityNotFoundException; import org.neo4j.kernel.api.index.IndexDescriptor; import org.neo4j.kernel.api.txstate.TransactionState; @@ -40,7 +43,6 @@ import org.neo4j.kernel.impl.api.legacyindex.InternalAutoIndexing; import org.neo4j.kernel.impl.api.store.StoreStatement; import org.neo4j.kernel.impl.index.LegacyIndexStore; -import org.neo4j.storageengine.api.NodeItem; import org.neo4j.storageengine.api.StoreReadLayer; import static org.junit.Assert.assertEquals; @@ -333,12 +335,7 @@ private void commitLabels( Labels... labels ) throws Exception for ( int label : nodeLabels.labelIds ) { - Collection nodes = allLabels.get( label ); - if ( nodes == null ) - { - nodes = new ArrayList<>(); - allLabels.put( label, nodes ); - } + Collection nodes = allLabels.computeIfAbsent( label, k -> new ArrayList<>() ); nodes.add( nodeLabels.nodeId ); } } @@ -360,25 +357,20 @@ private void commitLabels( Integer... labels ) throws Exception commitLabels( labels( nodeId, labels ) ); } - private void assertLabels( Integer... labels ) throws EntityNotFoundException + private void assertLabels( int... labels ) throws EntityNotFoundException { - try ( Cursor cursor = txContext.nodeCursorById( state, nodeId ) ) + txContext.nodeCursorById( state, nodeId ).forAll( node -> { - if ( cursor.next() ) - { - assertEquals( asSet( labels ), PrimitiveIntCollections.toSet( cursor.get().getLabels() ) ); - } - } + PrimitiveIntSet collect = node.labels().collect( Primitive.intSet(), IntSupplier::getAsInt ); + assertEquals( PrimitiveIntCollections.asSet( labels ), collect ); + } ); - for ( int label : labels ) + txContext.nodeCursorById( state, nodeId ).forAll( node -> { - try ( Cursor cursor = txContext.nodeCursorById( state, nodeId ) ) + for ( int label : labels ) { - if ( cursor.next() ) - { - assertTrue( "Expected labels not found on node", cursor.get().hasLabel( label ) ); - } + assertTrue( "Expected labels not found on node", node.hasLabel( label ) ); } - } + } ); } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/StubCursors.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/StubCursors.java index 19406a8e62104..fd05a1d13fe03 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/StubCursors.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/StubCursors.java @@ -26,7 +26,7 @@ import org.neo4j.cursor.Cursor; import org.neo4j.helpers.collection.Iterables; -import org.neo4j.kernel.api.cursor.NodeItemHelper; +import org.neo4j.kernel.api.cursor.EntityItemHelper; import org.neo4j.kernel.api.cursor.RelationshipItemHelper; import org.neo4j.kernel.api.properties.DefinedProperty; import org.neo4j.kernel.impl.util.Cursors; @@ -36,6 +36,9 @@ import org.neo4j.storageengine.api.NodeItem; import org.neo4j.storageengine.api.PropertyItem; import org.neo4j.storageengine.api.RelationshipItem; +import org.neo4j.storageengine.api.RelationshipTypeItem; + +import static org.neo4j.kernel.impl.util.Cursors.empty; /** * Stub cursors to be used for testing. @@ -44,167 +47,174 @@ public class StubCursors { public static Cursor asNodeCursor( final long nodeId ) { - return asNodeCursor( nodeId, Cursors.empty(), Cursors.empty() ); + return asNodeCursor( nodeId, empty(), empty() ); } - public static Cursor asNodeCursor( final long... nodeIds) + public static Cursor asNodeCursor( final long... nodeIds ) { NodeItem[] nodeItems = new NodeItem[nodeIds.length]; for (int i = 0; i < nodeIds.length; i++) { - nodeItems[i] = asNode( nodeIds[i] ); + nodeItems[i] = new StubNodeItem( nodeIds[i], empty(), empty() ); } - return Cursors.cursor( nodeItems); + return Cursors.cursor( nodeItems ); } public static Cursor asNodeCursor( final long nodeId, final Cursor propertyCursor, final Cursor labelCursor ) { - return Cursors.cursor( asNode( nodeId, propertyCursor, labelCursor ) ); + return Cursors.cursor( new StubNodeItem( nodeId, propertyCursor, labelCursor ) ); } - public static NodeItem asNode( final long nodeId ) + private static class StubNodeItem extends EntityItemHelper implements NodeItem { - return asNode( nodeId, Cursors.empty(), Cursors.empty() ); - } + private final long nodeId; + private final Cursor propertyCursor; + private final Cursor labelCursor; - public static NodeItem asNode( final long nodeId, - final Cursor propertyCursor, - final Cursor labelCursor ) - { - return new NodeItemHelper() + private StubNodeItem( long nodeId, Cursor propertyCursor, Cursor labelCursor ) { - @Override - public long id() - { - return nodeId; - } + this.nodeId = nodeId; + this.propertyCursor = propertyCursor; + this.labelCursor = labelCursor; + } - @Override - public Cursor label( final int labelId ) + @Override + public long id() + { + return nodeId; + } + + @Override + public Cursor label( final int labelId ) + { + return new Cursor() { - return new Cursor() - { - Cursor cursor = labels(); + Cursor cursor = labels(); - @Override - public boolean next() + @Override + public boolean next() + { + while ( cursor.next() ) { - while ( cursor.next() ) + if ( cursor.get().getAsInt() == labelId ) { - if ( cursor.get().getAsInt() == labelId ) - { - return true; - } + return true; } - - return false; } - @Override - public void close() - { - cursor.close(); - } + return false; + } - @Override - public LabelItem get() - { - return cursor.get(); - } - }; - } + @Override + public void close() + { + cursor.close(); + } - @Override - public Cursor labels() - { - return labelCursor; - } + @Override + public LabelItem get() + { + return cursor.get(); + } + }; + } - @Override - public Cursor property( final int propertyKeyId ) + @Override + public boolean hasLabel( int labelId ) + { + return label( labelId ).exists(); + } + + @Override + public Cursor labels() + { + return labelCursor; + } + + @Override + public Cursor property( final int propertyKeyId ) + { + return new Cursor() { - return new Cursor() - { - Cursor cursor = properties(); + Cursor cursor = properties(); - @Override - public boolean next() + @Override + public boolean next() + { + while ( cursor.next() ) { - while ( cursor.next() ) + if ( cursor.get().propertyKeyId() == propertyKeyId ) { - if ( cursor.get().propertyKeyId() == propertyKeyId ) - { - return true; - } + return true; } - - return false; } - @Override - public void close() - { - cursor.close(); - } + return false; + } - @Override - public PropertyItem get() - { - return cursor.get(); - } - }; - } + @Override + public void close() + { + cursor.close(); + } - @Override - public Cursor properties() - { - return propertyCursor; - } + @Override + public PropertyItem get() + { + return cursor.get(); + } + }; + } - @Override - public Cursor relationships( Direction direction, int... relTypes ) - { - throw new UnsupportedOperationException(); - } + @Override + public Cursor properties() + { + return propertyCursor; + } - @Override - public Cursor relationships( Direction direction ) - { - throw new UnsupportedOperationException(); - } + @Override + public Cursor relationships( Direction direction, int... relTypes ) + { + throw new UnsupportedOperationException(); + } - @Override - public Cursor relationshipTypes() - { - throw new UnsupportedOperationException(); - } + @Override + public Cursor relationships( Direction direction ) + { + throw new UnsupportedOperationException(); + } - @Override - public int degree( Direction direction ) - { - throw new UnsupportedOperationException(); - } + @Override + public Cursor relationshipTypes() + { + throw new UnsupportedOperationException(); + } - @Override - public int degree( Direction direction, int relType ) - { - throw new UnsupportedOperationException(); - } + @Override + public int degree( Direction direction ) + { + throw new UnsupportedOperationException(); + } - @Override - public boolean isDense() - { - throw new UnsupportedOperationException( ); - } + @Override + public int degree( Direction direction, int relType ) + { + throw new UnsupportedOperationException(); + } - @Override - public Cursor degrees() - { - throw new UnsupportedOperationException(); - } - }; + @Override + public boolean isDense() + { + throw new UnsupportedOperationException( ); + } + + @Override + public Cursor degrees() + { + throw new UnsupportedOperationException(); + } } public static RelationshipItem asRelationship( final long relId, final int type, diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/DiskLayerLabelTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/DiskLayerLabelTest.java index 6ebcc66934518..18073ffc8d8fe 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/DiskLayerLabelTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/DiskLayerLabelTest.java @@ -21,10 +21,14 @@ import org.junit.Test; +import java.util.Collection; import java.util.HashSet; +import java.util.Set; +import java.util.function.IntSupplier; +import org.neo4j.collection.primitive.Primitive; import org.neo4j.collection.primitive.PrimitiveIntCollections; -import org.neo4j.collection.primitive.PrimitiveIntIterator; +import org.neo4j.collection.primitive.PrimitiveIntSet; import org.neo4j.collection.primitive.PrimitiveLongCollections; import org.neo4j.collection.primitive.PrimitiveLongIterator; import org.neo4j.cursor.Cursor; @@ -63,11 +67,11 @@ public void should_be_able_to_list_labels_for_node() throws Exception } // THEN - Cursor node = disk.newStatement().acquireSingleNodeCursor( nodeId ); - node.next(); - PrimitiveIntIterator readLabels = node.get().getLabels(); - assertEquals( new HashSet<>( asList( labelId1, labelId2 ) ), - PrimitiveIntCollections.addToCollection( readLabels, new HashSet() ) ); + disk.newStatement().acquireSingleNodeCursor( nodeId ).forAll( node -> + { + PrimitiveIntSet actual = node.labels().collect( Primitive.intSet(), IntSupplier::getAsInt ); + assertEquals( PrimitiveIntCollections.asSet( new int[]{labelId1, labelId2} ), actual ); + } ); } @Test diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/StoreSingleNodeCursorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/StoreSingleNodeCursorTest.java index 115d55c13197e..9d41e87272578 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/StoreSingleNodeCursorTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/StoreSingleNodeCursorTest.java @@ -49,6 +49,7 @@ import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; import org.neo4j.kernel.impl.store.record.RelationshipRecord; import org.neo4j.storageengine.api.DegreeItem; +import org.neo4j.storageengine.api.RelationshipTypeItem; import org.neo4j.test.rule.DatabaseRule; import org.neo4j.test.rule.ImpermanentDatabaseRule; import org.neo4j.test.rule.RandomRule; @@ -463,7 +464,7 @@ private Set relTypes( StoreSingleNodeCursor cursor ) { Set types = new HashSet<>(); - Cursor relTypesCursor = cursor.relationshipTypes(); + Cursor relTypesCursor = cursor.relationshipTypes(); while ( relTypesCursor.next() ) { int typeId = relTypesCursor.get().getAsInt(); @@ -619,9 +620,9 @@ private void noNodeChange( long nodeId ) @SuppressWarnings( "unchecked" ) private StoreSingleNodeCursor newCursor( long nodeId ) { - StoreSingleNodeCursor cursor = new StoreSingleNodeCursor( new NodeRecord( -1 ), resolveNeoStores(), - mock( StoreStatement.class ), mock( Consumer.class ), new RecordCursors( resolveNeoStores() ), - NO_LOCK_SERVICE ); + StoreSingleNodeCursor cursor = + new StoreSingleNodeCursor( new NodeRecord( -1 ), resolveNeoStores(), mock( Consumer.class ), + new RecordCursors( resolveNeoStores() ), NO_LOCK_SERVICE ); cursor.init( nodeId ); assertTrue( cursor.next() ); diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreBehaviourTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreBehaviourTest.java index ed878b43a210c..a775f721b4074 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreBehaviourTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreBehaviourTest.java @@ -47,6 +47,7 @@ import org.neo4j.test.rule.ConfigurablePageCacheRule; import org.neo4j.test.rule.fs.EphemeralFileSystemRule; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.neo4j.helpers.collection.MapUtil.stringMap; @@ -258,6 +259,60 @@ public void pageCursorErrorsMustNotLingerInRecordCursor() throws Exception assertTrue( cursor.next( 2, new IntRecord( 2 ), NORMAL ) ); } + @Test + public void shouldReadTheCorrectRecordWhenGivenAnExplicitIdAndNotUseTheCurrentIdPointer() throws Exception + { + createStore(); + IntRecord record42 = new IntRecord( 42 ); + record42.value = 0x42; + store.updateRecord( record42 ); + IntRecord record43 = new IntRecord( 43 ); + record43.value = 0x43; + store.updateRecord( record43 ); + + RecordCursor cursor = store.newRecordCursor( new IntRecord( 1 ) ).acquire( 42, FORCE ); + + // we need to read record 43 not 42! + assertTrue( cursor.next( 43 ) ); + assertEquals( record43, cursor.get() ); + + IntRecord record = new IntRecord( -1 ); + assertTrue( cursor.next( 43, record, NORMAL ) ); + assertEquals( record43, record ); + + // next with id does not affect the old pointer either, so 42 is read now + assertTrue( cursor.next() ); + assertEquals( record42, cursor.get() ); + } + + @Test + public void shouldJumpAroundPageIds() throws Exception + { + createStore(); + IntRecord record42 = new IntRecord( 42 ); + record42.value = 0x42; + store.updateRecord( record42 ); + + int idOnAnotherPage = 43 + (2 * store.getRecordsPerPage() ); + IntRecord record43 = new IntRecord( idOnAnotherPage ); + record43.value = 0x43; + store.updateRecord( record43 ); + + RecordCursor cursor = store.newRecordCursor( new IntRecord( 1 ) ).acquire( 42, FORCE ); + + // we need to read record 43 not 42! + assertTrue( cursor.next( idOnAnotherPage ) ); + assertEquals( record43, cursor.get() ); + + IntRecord record = new IntRecord( -1 ); + assertTrue( cursor.next( idOnAnotherPage, record, NORMAL ) ); + assertEquals( record43, record ); + + // next with id does not affect the old pointer either, so 42 is read now + assertTrue( cursor.next() ); + assertEquals( record42, cursor.get() ); + } + @Test public void rebuildIdGeneratorSlowMustThrowOnPageOverflow() throws Exception { diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/NeoStoresTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/NeoStoresTest.java index 55282147f16d7..a6f33c64396d8 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/NeoStoresTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/NeoStoresTest.java @@ -361,19 +361,20 @@ public void testRels1() throws Exception ds.stop(); } - private void relDelete( long id ) throws Exception + private void relDelete( long id ) { - RelationshipVisitor visitor = new RelationshipVisitor() - { - @Override - public void visit( long relId, int type, long startNode, long endNode ) - { + RelationshipVisitor visitor = ( relId, type, startNode, endNode ) -> transaction.relationshipDoDelete( relId, type, startNode, endNode ); - } - }; if ( !transaction.relationshipVisit( id, visitor ) ) { - storeLayer.relationshipVisit( id, visitor ); + try + { + storeLayer.relationshipVisit( id, visitor ); + } + catch ( EntityNotFoundException e ) + { + throw new RuntimeException( e ); + } } } @@ -1327,13 +1328,14 @@ else if ( data.propertyKeyId() == prop3.propertyKeyId() ) private void assertHasRelationships( long node ) { - try ( KernelStatement statement = (KernelStatement) tx.acquireStatement(); Cursor nodeCursor = statement.getStoreStatement().acquireSingleNodeCursor( node ) ) { nodeCursor.next(); - PrimitiveLongIterator rels = nodeCursor.get().getRelationships( Direction.BOTH ); - assertTrue( rels.hasNext() ); + try( Cursor rels = nodeCursor.get().relationships( Direction.BOTH ) ) + { + assertTrue( rels.next() ); + } } } @@ -1450,12 +1452,8 @@ private void deleteRelationships( long nodeId ) throws Exception try ( KernelStatement statement = (KernelStatement) tx.acquireStatement(); Cursor nodeCursor = statement.getStoreStatement().acquireSingleNodeCursor( nodeId ) ) { - nodeCursor.next(); - PrimitiveLongIterator relationships = nodeCursor.get().getRelationships( Direction.BOTH ); - while ( relationships.hasNext() ) - { - relDelete( relationships.next() ); - } + assertTrue( nodeCursor.next() ); + nodeCursor.get().relationships( Direction.BOTH ).forAll( rel -> relDelete( rel.id() ) ); } } diff --git a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/PrimitiveIntVisitor.java b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/PrimitiveIntVisitor.java index 14aa4e6571b47..2cca342ab9972 100644 --- a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/PrimitiveIntVisitor.java +++ b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/PrimitiveIntVisitor.java @@ -19,6 +19,7 @@ */ package org.neo4j.collection.primitive; +@FunctionalInterface public interface PrimitiveIntVisitor { /** diff --git a/community/primitive-collections/src/main/java/org/neo4j/cursor/RawCursor.java b/community/primitive-collections/src/main/java/org/neo4j/cursor/RawCursor.java index 5efde36cd9eb1..0f5bb502f507f 100644 --- a/community/primitive-collections/src/main/java/org/neo4j/cursor/RawCursor.java +++ b/community/primitive-collections/src/main/java/org/neo4j/cursor/RawCursor.java @@ -19,8 +19,15 @@ */ package org.neo4j.cursor; +import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.Supplier; +import java.util.function.ToIntFunction; + +import org.neo4j.collection.primitive.PrimitiveIntCollection; +import org.neo4j.collection.primitive.PrimitiveIntSet; /** * A cursor is an object that moves to point to different locations in a data structure. @@ -49,13 +56,80 @@ public interface RawCursor extends Supplier, void close() throws EXCEPTION; default void forAll( Consumer consumer ) throws EXCEPTION + { + mapForAll( Function.identity(), consumer ); + } + + default R collect( R set, ToIntFunction map ) throws EXCEPTION + { + try + { + while ( next() ) + { + set.add( map.applyAsInt( get() ) ); + } + return set; + } + finally + { + close(); + } + } + + default E mapReduce( E initialValue, Function map, BiFunction reduce ) throws EXCEPTION + { + try + { + E current = initialValue; + while ( next() ) + { + current = reduce.apply( map.apply( get() ), current ); + } + return current; + } + finally + { + close(); + } + } + + default void mapForAll( Function function, Consumer consumer ) throws EXCEPTION { try { while ( next() ) { - consumer.accept( get() ); + consumer.accept( function.apply( get() ) ); + } + } + finally + { + close(); + } + } + + default boolean exists() throws EXCEPTION + { + try + { + return next(); + } + finally + { + close(); + } + } + + default int count() throws EXCEPTION + { + try + { + int count = 0; + while( next() ) + { + count++; } + return count; } finally {