diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/AllStoreHolder.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/AllStoreHolder.java index 61fc8bae6cbc8..2f42430cdc813 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/AllStoreHolder.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/AllStoreHolder.java @@ -706,6 +706,12 @@ void node( NodeRecord record, long reference, PageCursor pageCursor ) nodes.getRecordByCursor( reference, record, RecordLoad.CHECK, pageCursor ); } + @Override + void nodeAdvance( NodeRecord record, PageCursor pageCursor ) + { + nodes.nextRecordByCursor( record, RecordLoad.CHECK, pageCursor ); + } + @Override void relationship( RelationshipRecord record, long reference, PageCursor pageCursor ) { diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/DefaultNodeCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/DefaultNodeCursor.java index c8c3134747566..1ca24dd222a03 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/DefaultNodeCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/DefaultNodeCursor.java @@ -44,6 +44,7 @@ class DefaultNodeCursor extends NodeRecord implements NodeCursor private PageCursor pageCursor; private long next; private long highMark; + private long nextStoreReference; private HasChanges hasChanges = HasChanges.MAYBE; private LongSet addedNodes; @@ -67,6 +68,7 @@ void scan( Read read ) } this.next = 0; this.highMark = read.nodeHighMark(); + this.nextStoreReference = NO_ID; this.read = read; this.hasChanges = HasChanges.MAYBE; this.addedNodes = LongSets.immutable.empty(); @@ -85,6 +87,7 @@ void single( long reference, Read read ) this.next = reference; //This marks the cursor as a "single cursor" this.highMark = NO_ID; + this.nextStoreReference = NO_ID; this.read = read; this.hasChanges = HasChanges.MAYBE; this.addedNodes = LongSets.immutable.empty(); @@ -225,9 +228,16 @@ else if ( hasChanges && txs.nodeIsDeletedInThisTx( next ) ) next++; setInUse( false ); } + else if ( nextStoreReference == next ) + { + read.nodeAdvance( this, pageCursor ); + next++; + nextStoreReference++; + } else { read.node( this, next++, pageCursor ); + nextStoreReference = next; } if ( next > highMark ) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java index 4178f9087ccde..9c72b5eae03fb 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java @@ -587,6 +587,8 @@ public final void futureRelationshipPropertyReferenceRead( long reference ) abstract void node( NodeRecord record, long reference, PageCursor pageCursor ); + abstract void nodeAdvance( NodeRecord record, PageCursor pageCursor ); + abstract void relationship( RelationshipRecord record, long reference, PageCursor pageCursor ); abstract void relationshipFull( RelationshipRecord record, long reference, PageCursor pageCursor ); 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 517ab0a11041d..337aa642d0748 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 @@ -1042,7 +1042,6 @@ public void getRecordByCursor( long id, RECORD record, RecordLoad mode, PageCurs { throw new UnderlyingStorageException( e ); } - } void readIntoRecord( long id, RECORD record, RecordLoad mode, PageCursor cursor ) throws IOException @@ -1055,17 +1054,8 @@ void readIntoRecord( long id, RECORD record, RecordLoad mode, PageCursor cursor int offset = offsetForId( id ); if ( cursor.next( pageId ) ) { - // There is a page in the store that covers this record, go read it cursor.setOffset( offset ); - cursor.mark(); - do - { - prepareForReading( cursor, record ); - recordFormat.read( record, cursor, mode, recordSize ); - } - while ( cursor.shouldRetry() ); - checkForDecodingErrors( cursor, id, mode ); - verifyAfterReading( record, mode ); + readRecordFromPage( id, record, mode, cursor ); } else { @@ -1073,6 +1063,49 @@ void readIntoRecord( long id, RECORD record, RecordLoad mode, PageCursor cursor } } + @Override + public void nextRecordByCursor( RECORD record, RecordLoad mode, PageCursor cursor ) throws UnderlyingStorageException + { + assert cursor.getOffset() % recordSize == 0 : "Cursor offset is record start"; + assert cursor.getCurrentPageId() >= -1 : "Pages are assumed to be positive or -1 if not initialized"; + + try + { + int offset = cursor.getOffset(); + long id = record.getId() + 1; + record.setId( id ); + long pageId = cursor.getCurrentPageId(); + if ( offset >= storeFile.pageSize() || pageId < 0 ) + { + if ( !cursor.next( pageId + 1 ) ) + { + verifyAfterNotRead( record, mode ); + return; + } + cursor.setOffset( 0 ); + } + readRecordFromPage( id, record, mode, cursor ); + } + catch ( IOException e ) + { + throw new UnderlyingStorageException( e ); + } + } + + private void readRecordFromPage( long id, RECORD record, RecordLoad mode, PageCursor cursor ) + throws IOException + { + cursor.mark(); + do + { + prepareForReading( cursor, record ); + recordFormat.read( record, cursor, mode, recordSize ); + } + while ( cursor.shouldRetry() ); + checkForDecodingErrors( cursor, id, mode ); + verifyAfterReading( record, mode ); + } + @Override public void updateRecord( RECORD record ) { diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/RecordStore.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/RecordStore.java index b880ff760c1bd..678662b27e204 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/RecordStore.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/RecordStore.java @@ -130,6 +130,21 @@ public interface RecordStore extends IdSequen */ void getRecordByCursor( long id, RECORD target, RecordLoad mode, PageCursor cursor ) throws InvalidRecordException; + /** + * Reads a record from the store into {@code target}, see + * {@link RecordStore#getRecord(long, AbstractBaseRecord, RecordLoad)}. + *

+ * This method requires that the cursor page and offset point to the first byte of the record in target on calling. + * The provided page cursor will be used to get the record, and in doing this it will be redirected to the + * next page if the input record was the last on it's page. + * + * @param target the record to fill. + * @param mode loading behaviour, read more in {@link RecordStore#getRecord(long, AbstractBaseRecord, RecordLoad)}. + * @param cursor the PageCursor to use for record loading. + * @throws InvalidRecordException if record not in use and the {@code mode} allows for throwing. + */ + void nextRecordByCursor( RECORD target, RecordLoad mode, PageCursor cursor ) throws InvalidRecordException; + /** * For stores that have other stores coupled underneath, the "top level" record will have a flag * saying whether or not it's light. Light means that no records from the coupled store have been loaded yet. @@ -295,6 +310,12 @@ public void getRecordByCursor( long id, R target, RecordLoad mode, PageCursor cu actual.getRecordByCursor( id, target, mode, cursor ); } + @Override + public void nextRecordByCursor( R target, RecordLoad mode, PageCursor cursor ) throws InvalidRecordException + { + actual.nextRecordByCursor( target, mode, cursor ); + } + @Override public Collection getRecords( long firstId, RecordLoad mode ) throws InvalidRecordException { diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormat.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormat.java index c41e17efb8f15..e29183c6ca1ea 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormat.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormat.java @@ -19,8 +19,6 @@ */ package org.neo4j.kernel.impl.store.format.standard; -import java.io.IOException; - import org.neo4j.io.pagecache.PageCursor; import org.neo4j.kernel.impl.store.format.BaseOneByteHeaderRecordFormat; import org.neo4j.kernel.impl.store.format.BaseRecordFormat; @@ -32,6 +30,7 @@ public class NodeRecordFormat extends BaseOneByteHeaderRecordFormat { // in_use(byte)+next_rel_id(int)+next_prop_id(int)+labels(5)+extra(byte) public static final int RECORD_SIZE = 15; + private static final int HEADER_SIZE = 1; public NodeRecordFormat() { @@ -68,6 +67,11 @@ public void read( NodeRecord record, PageCursor cursor, RecordLoad mode, int rec BaseRecordFormat.longFromIntAndMod( nextProp, propModifier ), dense, BaseRecordFormat.longFromIntAndMod( nextRel, relModifier ), labels ); } + else + { + int nextOffset = cursor.getOffset() + recordSize - HEADER_SIZE; + cursor.setOffset( nextOffset ); + } } @Override diff --git a/community/kernel/src/main/java/org/neo4j/storageengine/api/StorageReader.java b/community/kernel/src/main/java/org/neo4j/storageengine/api/StorageReader.java index 4c23e419fa6e6..28970e7c383e0 100644 --- a/community/kernel/src/main/java/org/neo4j/storageengine/api/StorageReader.java +++ b/community/kernel/src/main/java/org/neo4j/storageengine/api/StorageReader.java @@ -602,6 +602,22 @@ interface RecordReads void getRecordByCursor( long reference, RECORD record, RecordLoad mode, PageCursor cursor ) throws InvalidRecordException; + /** + * Reads a record from the store into {@code target}, see + * {@link RecordStore#getRecord(long, AbstractBaseRecord, RecordLoad)}. + *

+ * This method requires that the cursor page and offset point to the first byte of the record in target on calling. + * The provided page cursor will be used to get the record, and in doing this it will be redirected to the + * next page if the input record was the last on it's page. + * + * @param record the record to fill. + * @param mode loading behaviour, read more in {@link RecordStore#getRecord(long, AbstractBaseRecord, RecordLoad)}. + * @param cursor the PageCursor to use for record loading. + * @throws InvalidRecordException if record not in use and the {@code mode} allows for throwing. + */ + void nextRecordByCursor( RECORD record, RecordLoad mode, PageCursor cursor ) + throws InvalidRecordException; + long getHighestPossibleIdInUse(); } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/MockStore.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/MockStore.java index c8792279a38e3..8de5d94fa3cad 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/MockStore.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/MockStore.java @@ -579,6 +579,12 @@ void node( NodeRecord record, long reference, PageCursor pageCursor ) initialize( record, reference, nodes ); } + @Override + void nodeAdvance( NodeRecord record, PageCursor pageCursor ) + { + initialize( record, record.getId() + 1, nodes ); + } + @Override void relationship( RelationshipRecord record, long reference, PageCursor pageCursor ) { diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java index a3c0ad4e6f51a..da540e232848b 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java @@ -160,6 +160,9 @@ public void read( RECORD record, PageCursor primaryCursor, RecordLoad mode, int record.setUseFixedReferences( isUseFixedReferences( headerByte ) ); doReadInternal( record, primaryCursor, recordSize, headerByte, inUse ); } + + // Set cursor offset to next record to prepare next read in case of scanning. + primaryCursor.setOffset( primaryStartOffset + recordSize ); } private boolean isUseFixedReferences( byte headerByte )