Skip to content

Commit

Permalink
Introduces 'additive' property of store capability
Browse files Browse the repository at this point in the history
So that it can be checked when comparing capabilities of two formats
Purely additive capability additions can be seen as compatible too.
  • Loading branch information
tinwelint committed Apr 3, 2018
1 parent 7b87fd9 commit c20c778
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 24 deletions.
Expand Up @@ -230,7 +230,7 @@ long record = getRecord( pageCache, neoStoreFileName, STORE_VERSION );
private boolean isCompatibleFormats( RecordFormats storeFormat ) private boolean isCompatibleFormats( RecordFormats storeFormat )
{ {
return FormatFamily.isSameFamily( recordFormats, storeFormat ) && return FormatFamily.isSameFamily( recordFormats, storeFormat ) &&
recordFormats.hasSameCapabilities( storeFormat, CapabilityType.FORMAT ) && recordFormats.hasCompatibleCapabilities( storeFormat, CapabilityType.FORMAT ) &&
recordFormats.generation() >= storeFormat.generation(); recordFormats.generation() >= storeFormat.generation();
} }


Expand Down
Expand Up @@ -19,6 +19,7 @@
*/ */
package org.neo4j.kernel.impl.store.format; package org.neo4j.kernel.impl.store.format;


import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;


Expand Down Expand Up @@ -123,19 +124,37 @@ public boolean hasCapability( Capability capability )
return contains( capabilities(), capability ); return contains( capabilities(), capability );
} }


public static boolean hasSameCapabilities( RecordFormats one, RecordFormats other, CapabilityType type ) public static boolean hasCompatibleCapabilities( RecordFormats one, RecordFormats other, CapabilityType type )
{ {
Set<Capability> myFormatCapabilities = Stream.of( one.capabilities() ) Set<Capability> myFormatCapabilities = Stream.of( one.capabilities() )
.filter( capability -> capability.isType( type ) ).collect( toSet() ); .filter( capability -> capability.isType( type ) ).collect( toSet() );
Set<Capability> otherFormatCapabilities = Stream.of( other.capabilities() ) Set<Capability> otherFormatCapabilities = Stream.of( other.capabilities() )
.filter( capability -> capability.isType( type ) ).collect( toSet() ); .filter( capability -> capability.isType( type ) ).collect( toSet() );


return myFormatCapabilities.equals( otherFormatCapabilities ); if ( myFormatCapabilities.equals( otherFormatCapabilities ) )
{
// If they have the same capabilities then of course they are compatible
return true;
}

Set<Capability> removedCapabilities =
new HashSet<>( myFormatCapabilities ).stream().filter( capability -> !otherFormatCapabilities.contains( capability ) ).collect( toSet() );
Set<Capability> addedCapabilities =
new HashSet<>( otherFormatCapabilities ).stream().filter( capability -> !myFormatCapabilities.contains( capability ) ).collect( toSet() );
boolean allAddedAreAdditive = addedCapabilities.stream().allMatch( Capability::isAdditive );
if ( removedCapabilities.isEmpty() && allAddedAreAdditive )
{
// Even if capabilities of the two aren't the same then there's a special case where if the additional
// capabilities of the other format are all additive then they are also compatible because no data
// in the existing store needs to be migrated.
return true;
}
return false;
} }


@Override @Override
public boolean hasSameCapabilities( RecordFormats other, CapabilityType type ) public boolean hasCompatibleCapabilities( RecordFormats other, CapabilityType type )
{ {
return hasSameCapabilities( this, other, type ); return hasCompatibleCapabilities( this, other, type );
} }
} }
Expand Up @@ -30,52 +30,65 @@ public enum Capability
/** /**
* Store has schema support * Store has schema support
*/ */
SCHEMA( CapabilityType.STORE ), SCHEMA( false, CapabilityType.STORE ),


/** /**
* Store has dense node support * Store has dense node support
*/ */
DENSE_NODES( CapabilityType.FORMAT, CapabilityType.STORE ), DENSE_NODES( false, CapabilityType.FORMAT, CapabilityType.STORE ),


/** /**
* 3 bytes relationship type support * 3 bytes relationship type support
*/ */
RELATIONSHIP_TYPE_3BYTES( CapabilityType.FORMAT, CapabilityType.STORE ), RELATIONSHIP_TYPE_3BYTES( false, CapabilityType.FORMAT, CapabilityType.STORE ),


/** /**
* Lucene version 3.x * Lucene version 3.x
*/ */
LUCENE_3( CapabilityType.INDEX ), LUCENE_3( false, CapabilityType.INDEX ),


/** /**
* Lucene version 5.x * Lucene version 5.x
*/ */
LUCENE_5( CapabilityType.INDEX ), LUCENE_5( false, CapabilityType.INDEX ),


/** /**
* Point Geometries are an addition to the format, not a change * Point Geometries are an addition to the format, not a change
*/ */
POINT_PROPERTIES( CapabilityType.STORE ), POINT_PROPERTIES( true, CapabilityType.STORE ),


/** /**
* Temporal types are an addition to the format, not a change * Temporal types are an addition to the format, not a change
*/ */
TEMPORAL_PROPERTIES( CapabilityType.STORE ), TEMPORAL_PROPERTIES( true, CapabilityType.STORE ),


/** /**
* Records can spill over into secondary units (another record with a header saying it's a secondary unit to another record). * Records can spill over into secondary units (another record with a header saying it's a secondary unit to another record).
*/ */
SECONDARY_RECORD_UNITS( CapabilityType.FORMAT ); SECONDARY_RECORD_UNITS( false, CapabilityType.FORMAT );


private final CapabilityType[] types; private final CapabilityType[] types;
private boolean additive;


Capability( CapabilityType... types ) Capability( boolean additive, CapabilityType... types )
{ {
this.additive = additive;
this.types = types; this.types = types;
} }


public boolean isType( CapabilityType type ) public boolean isType( CapabilityType type )
{ {
return contains( types, type ); return contains( types, type );
} }

/**
* Whether or not this capability is additive. A capability is additive if data regarding this capability will not change
* any existing store and therefore not require migration of existing data.
*
* @return whether or not this capability is additive.
*/
public boolean isAdditive()
{
return additive;
}
} }
Expand Up @@ -115,7 +115,7 @@ public Factory( String key, String... altKeys )
* @param type {@link CapabilityType type} of capability to compare. * @param type {@link CapabilityType type} of capability to compare.
* @return true if both formats have the same set of capabilities of the given {@code type}. * @return true if both formats have the same set of capabilities of the given {@code type}.
*/ */
boolean hasSameCapabilities( RecordFormats other, CapabilityType type ); boolean hasCompatibleCapabilities( RecordFormats other, CapabilityType type );


/** /**
* Record format name * Record format name
Expand Down
Expand Up @@ -68,7 +68,7 @@ public void migrate( File storeDir, File migrationDir, ProgressReporter progress
{ {
RecordFormats from = RecordFormatSelector.selectForVersion( versionToMigrateFrom ); RecordFormats from = RecordFormatSelector.selectForVersion( versionToMigrateFrom );
RecordFormats to = RecordFormatSelector.selectForVersion( versionToMigrateTo ); RecordFormats to = RecordFormatSelector.selectForVersion( versionToMigrateTo );
if ( !from.hasSameCapabilities( to, CapabilityType.INDEX ) ) if ( !from.hasCompatibleCapabilities( to, CapabilityType.INDEX ) )
{ {
originalExplicitIndexesRoot = indexImplementation.getIndexImplementationDirectory( storeDir ); originalExplicitIndexesRoot = indexImplementation.getIndexImplementationDirectory( storeDir );
migrationExplicitIndexesRoot = indexImplementation.getIndexImplementationDirectory( migrationDir ); migrationExplicitIndexesRoot = indexImplementation.getIndexImplementationDirectory( migrationDir );
Expand Down
Expand Up @@ -55,7 +55,7 @@ public void migrate( File storeDir, File migrationDir, ProgressReporter progress
{ {
RecordFormats from = RecordFormatSelector.selectForVersion( versionToMigrateFrom ); RecordFormats from = RecordFormatSelector.selectForVersion( versionToMigrateFrom );
RecordFormats to = RecordFormatSelector.selectForVersion( versionToMigrateTo ); RecordFormats to = RecordFormatSelector.selectForVersion( versionToMigrateTo );
if ( !from.hasSameCapabilities( to, CapabilityType.INDEX ) ) if ( !from.hasCompatibleCapabilities( to, CapabilityType.INDEX ) )
{ {
schemaIndexDirectory = indexProvider.directoryStructure().rootDirectory(); schemaIndexDirectory = indexProvider.directoryStructure().rootDirectory();
if ( schemaIndexDirectory != null ) if ( schemaIndexDirectory != null )
Expand Down
Expand Up @@ -209,7 +209,7 @@ public void migrate( File storeDir, File migrationDir, ProgressReporter progress


private boolean isDifferentCapabilities( RecordFormats oldFormat, RecordFormats newFormat ) private boolean isDifferentCapabilities( RecordFormats oldFormat, RecordFormats newFormat )
{ {
return !oldFormat.hasSameCapabilities( newFormat, CapabilityType.FORMAT ); return !oldFormat.hasCompatibleCapabilities( newFormat, CapabilityType.FORMAT );
} }


void writeLastTxInformation( File migrationDir, TransactionId txInfo ) throws IOException void writeLastTxInformation( File migrationDir, TransactionId txInfo ) throws IOException
Expand Down
@@ -0,0 +1,99 @@
/*
* Copyright (c) 2002-2018 "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 <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.impl.store.format;

import org.junit.Rule;
import org.junit.Test;

import org.neo4j.test.rule.RandomRule;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.neo4j.helpers.collection.Iterators.array;

public class BaseRecordFormatsTest
{
private static final Capability[] CAPABILITIES = Capability.values();
private static final CapabilityType[] CAPABILITY_TYPES = CapabilityType.values();

@Rule
public final RandomRule random = new RandomRule();

@Test
public void shouldReportCompatibilityBetweenTwoEqualSetsOfCapabilities()
{
// given
Capability[] capabilities = random.selection( CAPABILITIES, CAPABILITIES.length / 2, CAPABILITIES.length, false );

// then
assertCompatibility( capabilities, capabilities, true, CAPABILITY_TYPES );
}

@Test
public void shouldReportCompatibilityForAdditiveAdditionalCapabilities()
{
// given
Capability[] from = array( Capability.SCHEMA );
Capability[] to = array( Capability.SCHEMA, Capability.POINT_PROPERTIES, Capability.TEMPORAL_PROPERTIES );

// then
assertCompatibility( from, to, true, CAPABILITY_TYPES );
}

@Test
public void shouldReportIncompatibilityForChangingAdditionalCapabilities()
{
// given
Capability[] from = array( Capability.SCHEMA );
Capability[] to = array( Capability.SCHEMA, Capability.DENSE_NODES );

// then
assertCompatibility( from, to, false, CapabilityType.STORE );
}

@Test
public void shouldReportIncompatibilityForAdditiveRemovedCapabilities()
{
// given
Capability[] from = array( Capability.SCHEMA, Capability.POINT_PROPERTIES, Capability.TEMPORAL_PROPERTIES );
Capability[] to = array( Capability.SCHEMA );

// then
assertCompatibility( from, to, false, CapabilityType.STORE );
}

private void assertCompatibility( Capability[] from, Capability[] to, boolean compatible, CapabilityType... capabilityTypes )
{
for ( CapabilityType type : capabilityTypes )
{
assertEquals( compatible, format( from ).hasCompatibleCapabilities( format( to ), type ) );
}
}

private RecordFormats format( Capability... capabilities )
{
RecordFormats formats = mock( BaseRecordFormats.class );
when( formats.capabilities() ).thenReturn( capabilities );
when( formats.hasCompatibleCapabilities( any( RecordFormats.class ), any( CapabilityType.class ) ) ).thenCallRealMethod();
return formats;
}
}
Expand Up @@ -150,9 +150,9 @@ public FormatFamily getFormatFamily()
} }


@Override @Override
public boolean hasSameCapabilities( RecordFormats other, CapabilityType type ) public boolean hasCompatibleCapabilities( RecordFormats other, CapabilityType type )
{ {
return BaseRecordFormats.hasSameCapabilities( this, other, type ); return BaseRecordFormats.hasCompatibleCapabilities( this, other, type );
} }


@Override @Override
Expand Down
Expand Up @@ -187,7 +187,7 @@ public FormatFamily getFormatFamily()
} }


@Override @Override
public boolean hasSameCapabilities( RecordFormats other, CapabilityType type ) public boolean hasCompatibleCapabilities( RecordFormats other, CapabilityType type )
{ {
return false; return false;
} }
Expand Down
Expand Up @@ -153,9 +153,9 @@ public FormatFamily getFormatFamily()
} }


@Override @Override
public boolean hasSameCapabilities( RecordFormats other, CapabilityType type ) public boolean hasCompatibleCapabilities( RecordFormats other, CapabilityType type )
{ {
return actual.hasSameCapabilities( other, type ); return actual.hasCompatibleCapabilities( other, type );
} }


@Override @Override
Expand Down
Expand Up @@ -59,7 +59,7 @@ public class HighLimitStoreMigrationTest
@Test @Test
public void haveDifferentFormatCapabilitiesAsHighLimit3_0() public void haveDifferentFormatCapabilitiesAsHighLimit3_0()
{ {
assertFalse( HighLimit.RECORD_FORMATS.hasSameCapabilities( HighLimitV3_0_0.RECORD_FORMATS, CapabilityType.FORMAT ) ); assertFalse( HighLimit.RECORD_FORMATS.hasCompatibleCapabilities( HighLimitV3_0_0.RECORD_FORMATS, CapabilityType.FORMAT ) );
} }


@Test @Test
Expand Down

0 comments on commit c20c778

Please sign in to comment.