public static Collection<DynamicRecord> putSorted( NodeRecord node, long[] labelIds, NodeStore nodeStore, DynamicRecordAllocator allocator ) { if ( tryInlineInNodeRecord( node, labelIds, node.getDynamicLabelRecords() ) ) { return Collections.emptyList(); } return DynamicNodeLabels.putSorted( node, labelIds, nodeStore, allocator ); }
@Override public void updateRecord( NodeRecord record ) { super.updateRecord( record ); updateDynamicLabelRecords( record.getDynamicLabelRecords() ); }
@Override public Collection<DynamicRecord> remove( long labelId, NodeStore nodeStore ) { long[] newLabelIds = filter( parseInlined( node.getLabelField() ), labelId ); boolean inlined = tryInlineInNodeRecord( node, newLabelIds, node.getDynamicLabelRecords() ); assert inlined; return Collections.emptyList(); }
@Override public boolean visitNodeCommand( NodeCommand command ) { NodeStore nodeStore = neoStores.getNodeStore(); track( nodeStore, command ); track( nodeStore.getDynamicLabelStore(), command.getAfter().getDynamicLabelRecords() ); return false; }
static Collection<DynamicRecord> putSorted( NodeRecord node, long[] labelIds, NodeStore nodeStore, DynamicRecordAllocator allocator ) { long existingLabelsField = node.getLabelField(); long existingLabelsBits = parseLabelsBody( existingLabelsField ); Collection<DynamicRecord> changedDynamicRecords = node.getDynamicLabelRecords(); long labelField = node.getLabelField(); if ( fieldPointsToDynamicRecordOfLabels( labelField ) ) { // There are existing dynamic label records, get them nodeStore.ensureHeavy( node, existingLabelsBits ); changedDynamicRecords = node.getDynamicLabelRecords(); setNotInUse( changedDynamicRecords ); } if ( !InlineNodeLabels.tryInlineInNodeRecord( node, labelIds, changedDynamicRecords ) ) { Iterator<DynamicRecord> recycledRecords = changedDynamicRecords.iterator(); Collection<DynamicRecord> allocatedRecords = allocateRecordsForDynamicLabels( node.getId(), labelIds, new ReusableRecordsCompositeAllocator( recycledRecords, allocator ) ); // Set the rest of the previously set dynamic records as !inUse while ( recycledRecords.hasNext() ) { DynamicRecord removedRecord = recycledRecords.next(); removedRecord.setInUse( false ); allocatedRecords.add( removedRecord ); } node.setLabelField( dynamicPointer( allocatedRecords ), allocatedRecords ); changedDynamicRecords = allocatedRecords; } return changedDynamicRecords; }
/** * Deletes a node by its id, returning its properties which are now removed. * * @param nodeId The id of the node to delete. */ public void nodeDelete( long nodeId ) { NodeRecord nodeRecord = recordChangeSet.getNodeRecords().getOrLoad( nodeId, null ).forChangingData(); if ( !nodeRecord.inUse() ) { throw new IllegalStateException( "Unable to delete Node[" + nodeId + "] since it has already been deleted." ); } nodeRecord.setInUse( false ); nodeRecord.setLabelField( Record.NO_LABELS_FIELD.intValue(), markNotInUse( nodeRecord.getDynamicLabelRecords() ) ); getAndDeletePropertyChain( nodeRecord ); }
@Test public void shouldReadIdOfDynamicRecordFromDynamicLabelsField() { // GIVEN NodeRecord node = nodeRecordWithDynamicLabels( nodeStore, oneByteLongs( 5 ) ); DynamicRecord dynamicRecord = node.getDynamicLabelRecords().iterator().next(); // WHEN long dynRecordId = NodeLabelsField.firstDynamicLabelRecordId( node.getLabelField() ); // THEN assertEquals( dynamicRecord.getId(), dynRecordId ); }
@Test public void oneDynamicRecordShouldExtendIntoAnAdditionalIfTooManyLabels() { // GIVEN // will occupy 60B of data, i.e. one dynamic record NodeRecord node = nodeRecordWithDynamicLabels( nodeStore, oneByteLongs( 56 ) ); Collection<DynamicRecord> initialRecords = node.getDynamicLabelRecords(); NodeLabels nodeLabels = NodeLabelsField.parseLabelsField( node ); // WHEN Set<DynamicRecord> changedDynamicRecords = Iterables.asSet( nodeLabels.add( 1, nodeStore, nodeStore.getDynamicLabelStore() ) ); // THEN assertTrue( changedDynamicRecords.containsAll( initialRecords ) ); assertEquals( initialRecords.size() + 1, changedDynamicRecords.size() ); }
@Test public void shouldSerializeDynamicRecordsRemoved() throws Exception { channel.reset(); // Given NodeRecord before = new NodeRecord( 12, false, 1, 2 ); before.setInUse( true ); List<DynamicRecord> beforeDyn = singletonList( dynamicRecord( 0, true, true, -1L, LONG.intValue(), new byte[]{1, 2, 3, 4, 5, 6, 7, 8} ) ); before.setLabelField( dynamicPointer( beforeDyn ), beforeDyn ); NodeRecord after = new NodeRecord( 12, false, 2, 1 ); after.setInUse( true ); List<DynamicRecord> dynamicRecords = singletonList( dynamicRecord( 0, false, true, -1L, LONG.intValue(), new byte[]{ 1, 2, 3, 4, 5, 6, 7, 8} ) ); after.setLabelField( dynamicPointer( dynamicRecords ), dynamicRecords ); // When Command.NodeCommand cmd = new Command.NodeCommand( before, after ); cmd.serialize( channel ); Command.NodeCommand result = (Command.NodeCommand) commandReader.read( channel ); // Then assertThat( result, equalTo( cmd ) ); assertThat( result.getMode(), equalTo( cmd.getMode() ) ); assertThat( result.getBefore(), equalTo( cmd.getBefore() ) ); assertThat( result.getAfter(), equalTo( cmd.getAfter() ) ); // And dynamic records should be the same assertThat( result.getBefore().getDynamicLabelRecords(), equalTo( cmd.getBefore().getDynamicLabelRecords() ) ); assertThat( result.getAfter().getDynamicLabelRecords(), equalTo( cmd.getAfter().getDynamicLabelRecords() ) ); }
@Test public void shouldReallocateSomeOfPreviousDynamicRecords() { // GIVEN NodeRecord node = nodeRecordWithDynamicLabels( nodeStore, oneByteLongs( 5 ) ); Set<DynamicRecord> initialRecords = Iterables.asUniqueSet( node.getDynamicLabelRecords() ); NodeLabels nodeLabels = NodeLabelsField.parseLabelsField( node ); // WHEN Set<DynamicRecord> reallocatedRecords = Iterables.asUniqueSet( nodeLabels.put( fourByteLongs( 100 ), nodeStore, nodeStore.getDynamicLabelStore() ) ); // THEN assertTrue( reallocatedRecords.containsAll( initialRecords ) ); assertTrue( reallocatedRecords.size() > initialRecords.size() ); }
@Override public Collection<DynamicRecord> remove( long labelId, NodeStore nodeStore ) { nodeStore.ensureHeavy( node, firstDynamicLabelRecordId( node.getLabelField() ) ); long[] existingLabelIds = getDynamicLabelsArray( node.getUsedDynamicLabelRecords(), nodeStore.getDynamicLabelStore() ); long[] newLabelIds = filter( existingLabelIds, labelId ); Collection<DynamicRecord> existingRecords = node.getDynamicLabelRecords(); if ( InlineNodeLabels.tryInlineInNodeRecord( node, newLabelIds, existingRecords ) ) { setNotInUse( existingRecords ); } else { Collection<DynamicRecord> newRecords = allocateRecordsForDynamicLabels( node.getId(), newLabelIds, new ReusableRecordsCompositeAllocator( existingRecords, nodeStore.getDynamicLabelStore() ) ); node.setLabelField( dynamicPointer( newRecords ), existingRecords ); if ( !newRecords.equals( existingRecords ) ) { // One less dynamic record, mark that one as not in use for ( DynamicRecord record : existingRecords ) { if ( !newRecords.contains( record ) ) { record.setInUse( false ); } } } } return existingRecords; }
@Override public Collection<DynamicRecord> add( long labelId, NodeStore nodeStore, DynamicRecordAllocator allocator ) { nodeStore.ensureHeavy( node, firstDynamicLabelRecordId( node.getLabelField() ) ); long[] existingLabelIds = getDynamicLabelsArray( node.getUsedDynamicLabelRecords(), nodeStore.getDynamicLabelStore() ); long[] newLabelIds = LabelIdArray.concatAndSort( existingLabelIds, labelId ); Collection<DynamicRecord> existingRecords = node.getDynamicLabelRecords(); Collection<DynamicRecord> changedDynamicRecords = allocateRecordsForDynamicLabels( node.getId(), newLabelIds, new ReusableRecordsCompositeAllocator( existingRecords, allocator ) ); node.setLabelField( dynamicPointer( changedDynamicRecords ), changedDynamicRecords ); return changedDynamicRecords; }
@Test public void twoDynamicRecordsShouldShrinkToOneWhenRemoving() { // GIVEN // will occupy 61B of data, i.e. just two dynamic records NodeRecord node = nodeRecordWithDynamicLabels( nodeStore, oneByteLongs( 57 ) ); Collection<DynamicRecord> initialRecords = node.getDynamicLabelRecords(); NodeLabels nodeLabels = NodeLabelsField.parseLabelsField( node ); // WHEN List<DynamicRecord> changedDynamicRecords = Iterables.addToCollection( nodeLabels.remove( 255 /*Initial labels go from 255 and down to 255-58*/, nodeStore ), new ArrayList<>() ); // THEN assertEquals( initialRecords, changedDynamicRecords ); assertTrue( changedDynamicRecords.get( 0 ).inUse() ); assertFalse( changedDynamicRecords.get( 1 ).inUse() ); }
@Test public void shouldReallocateAllOfPreviousDynamicRecordsAndThenSome() { // GIVEN NodeRecord node = nodeRecordWithDynamicLabels( nodeStore, fourByteLongs( 100 ) ); Set<DynamicRecord> initialRecords = Iterables.asSet( cloned( node.getDynamicLabelRecords(), DynamicRecord.class ) ); NodeLabels nodeLabels = NodeLabelsField.parseLabelsField( node ); // WHEN Set<DynamicRecord> reallocatedRecords = Iterables.asUniqueSet( nodeLabels.put( fourByteLongs( 5 ), nodeStore, nodeStore.getDynamicLabelStore() ) ); // THEN assertTrue( "initial:" + initialRecords + ", reallocated:" + reallocatedRecords , initialRecords.containsAll( used( reallocatedRecords ) ) ); assertTrue( used( reallocatedRecords ).size() < initialRecords.size() ); }
private TransactionRecordState nodeWithDynamicLabelRecord( NeoStores store, AtomicLong nodeId, AtomicLong dynamicLabelRecordId ) { TransactionRecordState recordState = newTransactionRecordState( store ); nodeId.set( store.getNodeStore().nextId() ); int[] labelIds = new int[20]; for ( int i = 0; i < labelIds.length; i++ ) { int labelId = (int) store.getLabelTokenStore().nextId(); recordState.createLabelToken( "Label" + i, labelId ); labelIds[i] = labelId; } recordState.nodeCreate( nodeId.get() ); for ( int labelId : labelIds ) { recordState.addLabelToNode( labelId, nodeId.get() ); } // Extract the dynamic label record id (which is also a verification that we allocated one) NodeRecord node = Iterables.single( recordChangeSet.getNodeRecords().changes() ).forReadingData(); dynamicLabelRecordId.set( Iterables.single( node.getDynamicLabelRecords() ).getId() ); return recordState; }
@Test public void oneDynamicRecordShouldStoreItsOwner() { // GIVEN // will occupy 60B of data, i.e. one dynamic record Long nodeId = 24L; NodeRecord node = nodeRecordWithDynamicLabels( nodeId, nodeStore, oneByteLongs(56) ); Collection<DynamicRecord> initialRecords = node.getDynamicLabelRecords(); // WHEN Pair<Long,long[]> pair = DynamicNodeLabels.getDynamicLabelsArrayAndOwner( initialRecords, nodeStore.getDynamicLabelStore() ); // THEN assertEquals( nodeId, pair.first() ); }
@Override public void checkReference( RECORD record, NodeRecord nodeRecord, CheckerEngine<RECORD, REPORT> engine, RecordAccess records ) { if ( nodeRecord.inUse() ) { NodeLabels nodeLabels = NodeLabelsField.parseLabelsField( nodeRecord ); if ( nodeLabels instanceof DynamicNodeLabels ) { DynamicNodeLabels dynamicNodeLabels = (DynamicNodeLabels) nodeLabels; long firstRecordId = dynamicNodeLabels.getFirstDynamicRecordId(); RecordReference<DynamicRecord> firstRecordReference = records.nodeLabels( firstRecordId ); ExpectedNodeLabelsChecker expectedNodeLabelsChecker = new ExpectedNodeLabelsChecker( nodeRecord ); LabelChainWalker<RECORD,REPORT> checker = new LabelChainWalker<>( expectedNodeLabelsChecker ); engine.comparativeCheck( firstRecordReference, checker ); nodeRecord.getDynamicLabelRecords(); // I think this is empty in production } else { long[] storeLabels = nodeLabels.get( null ); REPORT report = engine.report(); validateLabelIds( nodeRecord, storeLabels, report ); } } else if ( indexLabels.length != 0 ) { engine.report().nodeNotInUse( nodeRecord ); } }
@Test public void cloneShouldProduceExactCopy() { // Given long relId = 1337L; long propId = 1338L; long inlinedLabels = 12L; NodeRecord node = new NodeRecord( 1L, false, relId, propId ); node.setLabelField( inlinedLabels, asList( new DynamicRecord( 1L ), new DynamicRecord( 2L ) ) ); node.setInUse( true ); // When NodeRecord clone = node.clone(); // Then assertEquals( node.inUse(), clone.inUse() ); assertEquals( node.getLabelField(), clone.getLabelField() ); assertEquals( node.getNextProp(), clone.getNextProp() ); assertEquals( node.getNextRel(), clone.getNextRel() ); assertThat( clone.getDynamicLabelRecords(), equalTo( node.getDynamicLabelRecords() ) ); }
@Test public void oneDynamicRecordShouldShrinkIntoInlinedWhenRemoving() { // GIVEN NodeRecord node = nodeRecordWithDynamicLabels( nodeStore, oneByteLongs( 5 ) ); Collection<DynamicRecord> initialRecords = node.getDynamicLabelRecords(); NodeLabels nodeLabels = NodeLabelsField.parseLabelsField( node ); // WHEN Collection<DynamicRecord> changedDynamicRecords = Iterables.asCollection( nodeLabels.remove( 255, nodeStore ) ); // THEN assertEquals( initialRecords, changedDynamicRecords ); assertFalse( Iterables.single( changedDynamicRecords ).inUse() ); assertEquals( inlinedLabelsLongRepresentation( 251, 252, 253, 254 ), node.getLabelField() ); }
private void writeNodeRecord( WritableChannel channel, NodeRecord record ) throws IOException { byte flags = bitFlags( bitFlag( record.inUse(), Record.IN_USE.byteValue() ), bitFlag( record.isCreated(), Record.CREATED_IN_TX ), bitFlag( record.requiresSecondaryUnit(), Record.REQUIRE_SECONDARY_UNIT ), bitFlag( record.hasSecondaryUnitId(), Record.HAS_SECONDARY_UNIT ), bitFlag( record.isUseFixedReferences(), Record.USES_FIXED_REFERENCE_FORMAT ) ); channel.put( flags ); if ( record.inUse() ) { channel.put( record.isDense() ? (byte) 1 : (byte) 0 ); channel.putLong( record.getNextRel() ).putLong( record.getNextProp() ); channel.putLong( record.getLabelField() ); if ( record.hasSecondaryUnitId() ) { channel.putLong( record.getSecondaryUnitId() ); } } // Always write dynamic label records because we want to know which ones have been deleted // especially if the node has been deleted. writeDynamicRecords( channel, record.getDynamicLabelRecords() ); } }