diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/AbstractBaseRecord.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/AbstractBaseRecord.java index 9f7f62ae97c1f..6b4ac6820574f 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/AbstractBaseRecord.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/AbstractBaseRecord.java @@ -41,6 +41,7 @@ public abstract class AbstractBaseRecord implements CloneableInPublic private boolean requiresSecondaryUnit; private boolean inUse; private boolean created; + private boolean useFixedReferences; protected AbstractBaseRecord( long id ) { @@ -54,6 +55,7 @@ protected AbstractBaseRecord initialize( boolean inUse ) this.created = false; this.secondaryUnitId = NO_ID; this.requiresSecondaryUnit = false; + this.useFixedReferences = false; return this; } @@ -69,6 +71,7 @@ public void clear() created = false; secondaryUnitId = NO_ID; requiresSecondaryUnit = false; + this.useFixedReferences = false; } public long getId() @@ -143,6 +146,16 @@ public final boolean isCreated() return created; } + public boolean isUseFixedReferences() + { + return useFixedReferences; + } + + public void setUseFixedReferences( boolean useFixedReferences ) + { + this.useFixedReferences = useFixedReferences; + } + @Override public int hashCode() { 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 3754fdb595fc5..e24bd6d9ecedf 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 @@ -84,6 +84,7 @@ abstract class BaseHighLimitRecordFormat static final long NULL = Record.NULL_REFERENCE.intValue(); static final int HEADER_BIT_RECORD_UNIT = 0b0000_0010; static final int HEADER_BIT_FIRST_RECORD_UNIT = 0b0000_0100; + private static final int HEADER_BIT_FIXED_REFERENCE = 0b0000_0100; protected BaseHighLimitRecordFormat( Function recordSize, int recordHeaderSize ) { @@ -140,6 +141,7 @@ public void read( RECORD record, PageCursor primaryCursor, RecordLoad mode, int } else { + record.setUseFixedReferences( has( headerByte, HEADER_BIT_FIXED_REFERENCE ) ); doReadInternal( record, primaryCursor, recordSize, headerByte, inUse ); } } @@ -164,7 +166,14 @@ public void write( RECORD record, PageCursor primaryCursor, int recordSize ) ") collides with format-generic header bits"; headerByte = set( headerByte, IN_USE_BIT, record.inUse() ); headerByte = set( headerByte, HEADER_BIT_RECORD_UNIT, record.requiresSecondaryUnit() ); - headerByte = set( headerByte, HEADER_BIT_FIRST_RECORD_UNIT, true ); + if ( record.requiresSecondaryUnit() ) + { + headerByte = set( headerByte, HEADER_BIT_FIRST_RECORD_UNIT, true ); + } + else + { + headerByte = set( headerByte, HEADER_BIT_FIXED_REFERENCE, record.isUseFixedReferences() ); + } primaryCursor.putByte( headerByte ); if ( record.requiresSecondaryUnit() ) @@ -232,18 +241,24 @@ public final void prepare( RECORD record, int recordSize, IdSequence idSequence { if ( record.inUse() ) { - int requiredLength = HEADER_BYTE + requiredDataLength( record ); - boolean requiresSecondaryUnit = requiredLength > recordSize; - record.setRequiresSecondaryUnit( requiresSecondaryUnit ); - if ( record.requiresSecondaryUnit() && !record.hasSecondaryUnitId() ) + record.setUseFixedReferences( canUseFixedReferences( record )); + if ( !record.isUseFixedReferences() ) { - // Allocate a new id at this point, but this is not the time to free this ID the the case where - // this record doesn't need this secondary unit anymore... that needs to be done when applying to store. - record.setSecondaryUnitId( idSequence.nextId() ); + int requiredLength = HEADER_BYTE + requiredDataLength( record ); + boolean requiresSecondaryUnit = requiredLength > recordSize; + record.setRequiresSecondaryUnit( requiresSecondaryUnit ); + if ( record.requiresSecondaryUnit() && !record.hasSecondaryUnitId() ) + { + // Allocate a new id at this point, but this is not the time to free this ID the the case where + // this record doesn't need this secondary unit anymore... that needs to be done when applying to store. + record.setSecondaryUnitId( idSequence.nextId() ); + } } } } + protected abstract boolean canUseFixedReferences( RECORD record ); + /** * Required length of the data in the given record (without the header byte). * diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java index fe62bfe558053..2dc21f525f1f0 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java @@ -22,6 +22,7 @@ import java.io.IOException; import org.neo4j.io.pagecache.PageCursor; +import org.neo4j.kernel.impl.store.format.BaseRecordFormat; import org.neo4j.kernel.impl.store.record.NodeRecord; import org.neo4j.kernel.impl.store.record.Record; @@ -69,13 +70,32 @@ protected void doReadInternal( NodeRecord record, PageCursor cursor, int recordS { // Interpret the header byte boolean dense = has( headerByte, DENSE_NODE_BIT ); + if ( record.isUseFixedReferences() ) + { + byte modifiers = cursor.getByte(); + long relModifier = (modifiers & 0b0000_0001L) << 32; + long propModifier = (modifiers & 0b0000_0110L) << 31; - // Now read the rest of the data. The adapter will take care of moving the cursor over to the - // other unit when we've exhausted the first one. - long nextRel = decodeCompressedReference( cursor, headerByte, HAS_RELATIONSHIP_BIT, NULL ); - long nextProp = decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL ); - long labelField = decodeCompressedReference( cursor, headerByte, HAS_LABELS_BIT, NULL_LABELS ); - record.initialize( inUse, nextProp, dense, nextRel, labelField ); + long nextRel = cursor.getInt() & 0xFFFFFFFFL; + long nextProp = cursor.getInt() & 0xFFFFFFFFL; + + long lsbLabels = cursor.getInt() & 0xFFFFFFFFL; + long hsbLabels = cursor.getByte() & 0xFF; // so that a negative byte won't fill the "extended" bits with ones. + long labels = lsbLabels | (hsbLabels << 32); + + record.initialize( inUse, + BaseRecordFormat.longFromIntAndMod( nextProp, propModifier ), dense, + BaseRecordFormat.longFromIntAndMod( nextRel, relModifier ), labels ); + } + else + { + // Now read the rest of the data. The adapter will take care of moving the cursor over to the + // other unit when we've exhausted the first one. + long nextRel = decodeCompressedReference( cursor, headerByte, HAS_RELATIONSHIP_BIT, NULL ); + long nextProp = decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL ); + long labelField = decodeCompressedReference( cursor, headerByte, HAS_LABELS_BIT, NULL_LABELS ); + record.initialize( inUse, nextProp, dense, nextRel, labelField ); + } } @Override @@ -97,12 +117,44 @@ protected byte headerBits( NodeRecord record ) return header; } + @Override + protected boolean canUseFixedReferences( NodeRecord record ) + { + return (((record.getNextProp() == NULL) || ((record.getNextProp() & 0xFFFF_FFFC_0000_0000L) == 0)) && + ((record.getNextRel() == NULL) || ((record.getNextRel() & 0xFFFF_FFFE_0000_0000L) == 0))); + } + @Override protected void doWriteInternal( NodeRecord record, PageCursor cursor ) throws IOException { - encode( cursor, record.getNextRel(), NULL ); - encode( cursor, record.getNextProp(), NULL ); - encode( cursor, record.getLabelField(), NULL_LABELS ); + if ( record.isUseFixedReferences() ) + { + long nextRel = record.getNextRel(); + long nextProp = record.getNextProp(); + + short relModifier = nextRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (short)((nextRel & 0x1_0000_0000L) >> 32); + short propModifier = nextProp == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (short) ((nextProp & 0x3_0000_0000L) >> 31); + + // [ ,xxxx] higher bits for rel id + // [xxxx, ] higher bits for prop id + short modifiers = (short) ( relModifier | propModifier ); + + cursor.putByte( (byte) modifiers ); + cursor.putInt( (int) nextRel ); + cursor.putInt( (int) nextProp ); + + // lsb of labels + long labelField = record.getLabelField(); + cursor.putInt( (int) labelField ); + // msb of labels + cursor.putByte( (byte) ((labelField & 0xFF_0000_0000L) >> 32) ); + } + else + { + encode( cursor, record.getNextRel(), NULL ); + encode( cursor, record.getNextProp(), NULL ); + encode( cursor, record.getLabelField(), NULL_LABELS ); + } } } diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java index c836303510095..1e4e1d30308a6 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java @@ -23,12 +23,15 @@ import org.neo4j.io.pagecache.PageCursor; import org.neo4j.kernel.impl.store.format.BaseOneByteHeaderRecordFormat; +import org.neo4j.kernel.impl.store.format.BaseRecordFormat; import org.neo4j.kernel.impl.store.record.PropertyBlock; import org.neo4j.kernel.impl.store.record.PropertyRecord; +import org.neo4j.kernel.impl.store.record.Record; import org.neo4j.kernel.impl.store.record.RecordLoad; import static org.neo4j.kernel.impl.store.format.highlimit.Reference.toAbsolute; import static org.neo4j.kernel.impl.store.format.highlimit.Reference.toRelative; +import static org.neo4j.kernel.impl.store.record.Record.NULL_REFERENCE; /** @@ -68,13 +71,31 @@ public void read( PropertyRecord record, PageCursor cursor, RecordLoad mode, int int offset = cursor.getOffset(); byte headerByte = cursor.getByte(); boolean inUse = isInUse( headerByte ); + record.setUseFixedReferences( has( headerByte, 0b0000_0100 )); if ( mode.shouldLoad( inUse ) ) { int blockCount = headerByte >>> 4; long recordId = record.getId(); - record.initialize( inUse, - toAbsolute( Reference.decode( cursor ), recordId ), - toAbsolute( Reference.decode( cursor ), recordId ) ); + + if (record.isUseFixedReferences()) + { + // since fixed reference limits property reference to 34 bits, 6 bytes is ample. + long prevMod = (cursor.getShort() & 0xFFFFL); + long prevProp = cursor.getInt() & 0xFFFFFFFFL; + long nextMod = (cursor.getShort() & 0xFFFFL); + long nextProp = cursor.getInt() & 0xFFFFFFFFL; + record.initialize( true, + BaseRecordFormat.longFromIntAndMod( prevProp, prevMod << 32 ), + BaseRecordFormat.longFromIntAndMod( nextProp, nextMod << 32 ) ); + //skip 3 bytes before start reading property blocks + cursor.setOffset(cursor.getOffset() + 3); + } + else + { + record.initialize( inUse, + toAbsolute( Reference.decode( cursor ), recordId ), + toAbsolute( Reference.decode( cursor ), recordId ) ); + } if ( (blockCount > record.getBlockCapacity()) | (RECORD_SIZE - (cursor.getOffset() - offset) < blockCount * Long.BYTES) ) { cursor.setCursorException( "PropertyRecord claims to contain more blocks than can fit in a record" ); @@ -93,10 +114,31 @@ public void write( PropertyRecord record, PageCursor cursor, int recordSize ) { if ( record.inUse() ) { - cursor.putByte( (byte) ((record.inUse() ? IN_USE_BIT : 0) | numberOfBlocks( record ) << 4) ); + byte headerByte = (byte) ((record.inUse() ? IN_USE_BIT : 0) | numberOfBlocks( record ) << 4); + boolean canUseFixedReferences = canUseFixedReferences( record ); + record.setUseFixedReferences( canUseFixedReferences ); + headerByte = set( headerByte, 0b0000_0100, canUseFixedReferences ); + cursor.putByte( headerByte ); + long recordId = record.getId(); - Reference.encode( toRelative( record.getPrevProp(), recordId), cursor ); - Reference.encode( toRelative( record.getNextProp(), recordId), cursor ); + + if ( canUseFixedReferences ) + { + // Set up the record header + short prevModifier = record.getPrevProp() == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (short) ((record.getPrevProp() & 0xFFFF_0000_0000L) >> 32); + short nextModifier = record.getNextProp() == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (short) ((record.getNextProp() & 0xFFFF_0000_0000L) >> 32); + cursor.putShort( prevModifier ); + cursor.putInt( (int) record.getPrevProp() ); + cursor.putShort( nextModifier ); + cursor.putInt( (int) record.getNextProp() ); + //just stuff 3 bytes + cursor.putBytes(new byte[]{0,0,0}); + } + else + { + Reference.encode( toRelative( record.getPrevProp(), recordId ), cursor ); + Reference.encode( toRelative( record.getNextProp(), recordId ), cursor ); + } for ( PropertyBlock block : record ) { for ( long propertyBlock : block.getValueBlocks() ) @@ -126,4 +168,10 @@ public long getNextRecordReference( PropertyRecord record ) { return record.getNextProp(); } + + private boolean canUseFixedReferences( PropertyRecord record ) + { + return (((record.getNextProp() == NULL_REFERENCE.intValue()) || ((record.getNextProp() & 0xFFFF_FFFC_0000_0000L) == 0)) && + ((record.getPrevProp() == NULL_REFERENCE.intValue()) || ((record.getPrevProp() & 0xFFFF_FFFE_0000_0000L) == 0))); + } } diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java index 8863e1ac38066..ae258be8b5a0c 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java @@ -22,6 +22,8 @@ import java.io.IOException; import org.neo4j.io.pagecache.PageCursor; +import org.neo4j.kernel.impl.store.format.BaseRecordFormat; +import org.neo4j.kernel.impl.store.record.Record; import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; /** @@ -68,13 +70,47 @@ public RelationshipGroupRecord newRecord() protected void doReadInternal( RelationshipGroupRecord record, PageCursor cursor, int recordSize, long headerByte, boolean inUse ) { - record.initialize( inUse, - cursor.getShort() & 0xFFFF, - decodeCompressedReference( cursor, headerByte, HAS_OUTGOING_BIT, NULL ), - decodeCompressedReference( cursor, headerByte, HAS_INCOMING_BIT, NULL ), - decodeCompressedReference( cursor, headerByte, HAS_LOOP_BIT, NULL ), - decodeCompressedReference( cursor ), - decodeCompressedReference( cursor, headerByte, HAS_NEXT_BIT, NULL ) ); + if ( record.isUseFixedReferences() ) + { + + // [ , x] high next bits + // [ , x ] high firstOut bits + // [ , x ] high firstIn bits + // [ ,x ] high firstLoop bits + // [ x, ] high owner bits + long modifiers = cursor.getByte(); + + int type = cursor.getShort() & 0xFFFF; + + long nextLowBits = cursor.getInt() & 0xFFFFFFFFL; + long nextOutLowBits = cursor.getInt() & 0xFFFFFFFFL; + long nextInLowBits = cursor.getInt() & 0xFFFFFFFFL; + long nextLoopLowBits = cursor.getInt() & 0xFFFFFFFFL; + long owningNode = cursor.getInt() & 0xFFFFFFFFL; + + long nextMod = (modifiers & 0b0000_0001) << 32; + long nextOutMod = (modifiers & 0b0000_0010) << 31; + long nextInMod = (modifiers & 0b0000_0100) << 30; + long nextLoopMod = (modifiers & 0b0000_1000) << 29; + long owningNodeMod = (modifiers & 0b0001_0000) << 28; + + record.initialize( inUse, type, + BaseRecordFormat.longFromIntAndMod( nextOutLowBits, nextOutMod ), + BaseRecordFormat.longFromIntAndMod( nextInLowBits, nextInMod ), + BaseRecordFormat.longFromIntAndMod( nextLoopLowBits, nextLoopMod ), + BaseRecordFormat.longFromIntAndMod( owningNode, owningNodeMod ), + BaseRecordFormat.longFromIntAndMod( nextLowBits, nextMod ) ); + } + else + { + record.initialize( inUse, + cursor.getShort() & 0xFFFF, + decodeCompressedReference( cursor, headerByte, HAS_OUTGOING_BIT, NULL ), + decodeCompressedReference( cursor, headerByte, HAS_INCOMING_BIT, NULL ), + decodeCompressedReference( cursor, headerByte, HAS_LOOP_BIT, NULL ), + decodeCompressedReference( cursor ), + decodeCompressedReference( cursor, headerByte, HAS_NEXT_BIT, NULL ) ); + } } @Override @@ -103,11 +139,46 @@ protected int requiredDataLength( RelationshipGroupRecord record ) protected void doWriteInternal( RelationshipGroupRecord record, PageCursor cursor ) throws IOException { - cursor.putShort( (short) record.getType() ); - encode( cursor, record.getFirstOut(), NULL ); - encode( cursor, record.getFirstIn(), NULL ); - encode( cursor, record.getFirstLoop(), NULL ); - encode( cursor, record.getOwningNode() ); - encode( cursor, record.getNext(), NULL ); + if ( record.isUseFixedReferences() ) + { + long nextMod = record.getNext() == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (record.getNext() & 0x100000000L) >> 32; + long nextOutMod = record.getFirstOut() == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (record.getFirstOut() & 0x100000000L) >> 31; + long nextInMod = record.getFirstIn() == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (record.getFirstIn() & 0x100000000L) >> 30; + long nextLoopMod = record.getFirstLoop() == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (record.getFirstLoop() & 0x100000000L) >> 29; + long ownerMod = record.getOwningNode() == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (record.getOwningNode() & 0x100000000L) >> 28; + + // [ , x] high next bits + // [ , x ] high firstOut bits + // [ , x ] high firstIn bits + // [ ,x ] high firstLoop bits + // [ x, ] high owner bits + cursor.putByte( (byte) (nextMod | nextOutMod | nextInMod | nextLoopMod | ownerMod) ); + + cursor.putShort( (short) record.getType() ); + cursor.putInt( (int) record.getNext() ); + cursor.putInt( (int) record.getFirstOut() ); + cursor.putInt( (int) record.getFirstIn() ); + cursor.putInt( (int) record.getFirstLoop() ); + cursor.putInt( (int) record.getOwningNode() ); + } + else + { + cursor.putShort( (short) record.getType() ); + encode( cursor, record.getFirstOut(), NULL ); + encode( cursor, record.getFirstIn(), NULL ); + encode( cursor, record.getFirstLoop(), NULL ); + encode( cursor, record.getOwningNode() ); + encode( cursor, record.getNext(), NULL ); + } + } + + @Override + protected boolean canUseFixedReferences( RelationshipGroupRecord record ) + { + return !((record.getNext() != NULL) && ((record.getNext() & 0xFFFF_FFFE_0000_0000L) != 0) || + (record.getFirstOut() != NULL) && ((record.getFirstOut() & 0xFFFF_FFFE_0000_0000L) != 0) || + (record.getFirstIn() != NULL) && ((record.getFirstIn() & 0xFFFF_FFFE_0000_0000L) != 0) || + (record.getFirstLoop() != NULL) && ((record.getFirstLoop() & 0xFFFF_FFFE_0000_0000L) != 0) || + (record.getOwningNode() != NULL) && ((record.getOwningNode() & 0xFFFF_FFFE_0000_0000L) != 0)); } } diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java index 95c259509ed89..207cd67d1c31b 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java @@ -22,6 +22,8 @@ import java.io.IOException; import org.neo4j.io.pagecache.PageCursor; +import org.neo4j.kernel.impl.store.format.BaseRecordFormat; +import org.neo4j.kernel.impl.store.record.Record; import org.neo4j.kernel.impl.store.record.RelationshipRecord; import static org.neo4j.kernel.impl.store.format.highlimit.Reference.toAbsolute; @@ -76,17 +78,64 @@ protected void doReadInternal( { int type = cursor.getShort() & 0xFFFF; long recordId = record.getId(); - record.initialize( inUse, - decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL ), - decodeCompressedReference( cursor ), - decodeCompressedReference( cursor ), - type, - decodeAbsoluteOrRelative( cursor, headerByte, FIRST_IN_FIRST_CHAIN_BIT, recordId ), - decodeAbsoluteIfPresent( cursor, headerByte, HAS_FIRST_CHAIN_NEXT_BIT, recordId ), - decodeAbsoluteOrRelative( cursor, headerByte, FIRST_IN_SECOND_CHAIN_BIT, recordId ), - decodeAbsoluteIfPresent( cursor, headerByte, HAS_SECOND_CHAIN_NEXT_BIT, recordId ), - has( headerByte, FIRST_IN_FIRST_CHAIN_BIT ), - has( headerByte, FIRST_IN_SECOND_CHAIN_BIT ) ); + if (record.isUseFixedReferences()) + { + // [ , x] first node higher order bits + // [ , x ] second node high order bits + // [ , x ] first prev high order bits + // [ ,x ] first next high order bits + // [ x, ] second prev high order bits + // [ x , ] second next high order bits + // [xx , ] next prop high order bits + long modifiers = cursor.getByte(); + + long firstNode = cursor.getInt() & 0xFFFFFFFFL; + long firstNodeMod = (modifiers & 0b0000_0001L) << 32; + + long secondNode = cursor.getInt() & 0xFFFFFFFFL; + long secondNodeMod = (modifiers & 0b0000_0010L) << 31; + + long firstPrevRel = cursor.getInt() & 0xFFFFFFFFL; + long firstPrevRelMod = (modifiers & 0b0000_0100L) << 30; + + long firstNextRel = cursor.getInt() & 0xFFFFFFFFL; + long firstNextRelMod = (modifiers & 0b0000_1000L) << 29; + + long secondPrevRel = cursor.getInt() & 0xFFFFFFFFL; + long secondPrevRelMod = (modifiers & 0b0001_0000L) << 28; + + long secondNextRel = cursor.getInt() & 0xFFFFFFFFL; + long secondNextRelMod = (modifiers & 0b0010_0000L) << 27; + + long nextProp = cursor.getInt() & 0xFFFFFFFFL; + long nextPropMod = (modifiers & 0b1100_0000L) << 26; + + record.initialize( inUse, + BaseRecordFormat.longFromIntAndMod( nextProp, nextPropMod ), + BaseRecordFormat.longFromIntAndMod( firstNode, firstNodeMod ), + BaseRecordFormat.longFromIntAndMod( secondNode, secondNodeMod ), + type, + BaseRecordFormat.longFromIntAndMod( firstPrevRel, firstPrevRelMod ), + BaseRecordFormat.longFromIntAndMod( firstNextRel, firstNextRelMod ), + BaseRecordFormat.longFromIntAndMod( secondPrevRel, secondPrevRelMod ), + BaseRecordFormat.longFromIntAndMod( secondNextRel, secondNextRelMod ), + has( headerByte, FIRST_IN_FIRST_CHAIN_BIT ), + has( headerByte, FIRST_IN_SECOND_CHAIN_BIT ) ); + } + else + { + record.initialize( inUse, + decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL ), + decodeCompressedReference( cursor ), + decodeCompressedReference( cursor ), + type, + decodeAbsoluteOrRelative( cursor, headerByte, FIRST_IN_FIRST_CHAIN_BIT, recordId ), + decodeAbsoluteIfPresent( cursor, headerByte, HAS_FIRST_CHAIN_NEXT_BIT, recordId ), + decodeAbsoluteOrRelative( cursor, headerByte, FIRST_IN_SECOND_CHAIN_BIT, recordId ), + decodeAbsoluteIfPresent( cursor, headerByte, HAS_SECOND_CHAIN_NEXT_BIT, recordId ), + has( headerByte, FIRST_IN_FIRST_CHAIN_BIT ), + has( headerByte, FIRST_IN_SECOND_CHAIN_BIT ) ); + } } private long decodeAbsoluteOrRelative( PageCursor cursor, long headerByte, int firstInStartBit, long recordId ) @@ -128,22 +177,83 @@ protected void doWriteInternal( RelationshipRecord record, PageCursor cursor ) { cursor.putShort( (short) record.getType() ); long recordId = record.getId(); - encode( cursor, record.getNextProp(), NULL ); - encode( cursor, record.getFirstNode() ); - encode( cursor, record.getSecondNode() ); - - encode( cursor, getFirstPrevReference( record, recordId ) ); - if ( record.getFirstNextRel() != NULL ) + if (record.isUseFixedReferences()) { - encode( cursor, toRelative( record.getFirstNextRel(), recordId ) ); + long firstNode = record.getFirstNode(); + short firstNodeMod = (short)((firstNode & 0x1_0000_0000L) >> 32); + + long secondNode = record.getSecondNode(); + long secondNodeMod = (secondNode & 0x1_0000_0000L) >> 31; + + long firstPrevRel = record.getFirstPrevRel(); + long firstPrevRelMod = firstPrevRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (firstPrevRel & 0x1_0000_0000L) >> 30; + + long firstNextRel = record.getFirstNextRel(); + long firstNextRelMod = firstNextRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (firstNextRel & 0x1_0000_0000L) >> 29; + + long secondPrevRel = record.getSecondPrevRel(); + long secondPrevRelMod = secondPrevRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (secondPrevRel & 0x1_0000_0000L) >> 28; + + long secondNextRel = record.getSecondNextRel(); + long secondNextRelMod = secondNextRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (secondNextRel & 0x1_0000_0000L) >> 27; + + long nextProp = record.getNextProp(); + long nextPropMod = nextProp == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (nextProp & 0x3_0000_0000L) >> 26; + + // [ , x] first node higher order bits + // [ , x ] second node high order bits + // [ , x ] first prev high order bits + // [ ,x ] first next high order bits + // [ x, ] second prev high order bits + // [ x , ] second next high order bits + // [xx , ] next prop high order bits + short modifiers = (short) (firstNodeMod | secondNodeMod | firstPrevRelMod | firstNextRelMod | + secondPrevRelMod | secondNextRelMod | nextPropMod); + + cursor.putByte( (byte)modifiers ); + cursor.putInt( (int) firstNode ); + cursor.putInt( (int) secondNode ); + cursor.putInt( (int) firstPrevRel ); + cursor.putInt( (int) firstNextRel ); + cursor.putInt( (int) secondPrevRel ); + cursor.putInt( (int) secondNextRel ); + cursor.putInt( (int) nextProp ); } - encode( cursor, getSecondPrevReference( record, recordId ) ); - if ( record.getSecondNextRel() != NULL ) + else { - encode( cursor, toRelative( record.getSecondNextRel(), recordId ) ); + encode( cursor, record.getNextProp(), NULL ); + encode( cursor, record.getFirstNode() ); + encode( cursor, record.getSecondNode() ); + + encode( cursor, getFirstPrevReference( record, recordId ) ); + if ( record.getFirstNextRel() != NULL ) + { + encode( cursor, toRelative( record.getFirstNextRel(), recordId ) ); + } + encode( cursor, getSecondPrevReference( record, recordId ) ); + if ( record.getSecondNextRel() != NULL ) + { + encode( cursor, toRelative( record.getSecondNextRel(), recordId ) ); + } } } + @Override + protected boolean canUseFixedReferences( RelationshipRecord record ) + { + return ((record.getFirstNode() & 0xFFFF_FFFE_0000_0000L) == 0) && + ((record.getSecondNode() & 0xFFFF_FFFE_0000_0000L) == 0) && + ((record.getFirstPrevRel() == NULL) || + ((record.getFirstPrevRel() & 0xFFFF_FFFE_0000_0000L) == 0)) && + ((record.getFirstNextRel() == NULL) || + ((record.getFirstNextRel() & 0xFFFF_FFFE_0000_0000L) == 0)) && + ((record.getSecondPrevRel() == NULL) || + ((record.getSecondPrevRel() & 0xFFFF_FFFE_0000_0000L) == 0)) && + ((record.getSecondNextRel() == NULL) || + ((record.getSecondNextRel() & 0xFFFF_FFFE_0000_0000L) == 0)) && + ((record.getNextProp() == NULL) || ((record.getNextProp() & 0xFFFF_FFFC_0000_0000L) == 0)); + } + private long getSecondPrevReference( RelationshipRecord record, long recordId ) { return record.isFirstInSecondChain() ? record.getSecondPrevRel() : diff --git a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java index 8a25a6e513279..eaec2a2d7e4ff 100644 --- a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java +++ b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java @@ -139,6 +139,12 @@ protected byte headerBits( MyRecord record ) return 0; } + @Override + protected boolean canUseFixedReferences( MyRecord record ) + { + return false; + } + @Override protected int requiredDataLength( MyRecord record ) { diff --git a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormatTest.java b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormatTest.java index ab295ea3e7a19..29f9e038ad15f 100644 --- a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormatTest.java +++ b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormatTest.java @@ -78,7 +78,7 @@ private PropertyRecord createRecord( PropertyRecordFormat format, long recordId record.setInUse( true ); record.setId( recordId ); record.setNextProp( 1L ); - record.setPrevProp( 3L ); + record.setPrevProp( (Integer.MAX_VALUE + 1L) << 3 ); return record; } }