Skip to content

Commit

Permalink
Add tests to prove that the concistency checker can find fulltext ind…
Browse files Browse the repository at this point in the history
…ex inconsistencies, and add relationship index checking to the consistency checker.
  • Loading branch information
chrisvest committed Sep 5, 2018
1 parent 3bc3c99 commit c6a9755
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 23 deletions.
Expand Up @@ -40,8 +40,9 @@ public enum CheckStage implements Stage
Stage7_RS_Backward( true, false, "RelationshipStore pass - reverse scan of source chain using the cache", 1, 1, Stage7_RS_Backward( true, false, "RelationshipStore pass - reverse scan of source chain using the cache", 1, 1,
ID_SLOT_SIZE, ID_SLOT_SIZE, 1 ), ID_SLOT_SIZE, ID_SLOT_SIZE, 1 ),
Stage8_PS_Props( true, true, "PropertyStore and Node to Index check pass" ), Stage8_PS_Props( true, true, "PropertyStore and Node to Index check pass" ),
Stage9_NS_LabelCounts( true, true, "NodeStore pass - Label counts" ), Stage9_RS_Indexes( true, true, "Relationship to Index check pass" ),
Stage10_NS_PropertyRelocator( true, true, "Property store relocation" ); Stage10_NS_LabelCounts( true, true, "NodeStore pass - Label counts" ),
Stage11_NS_PropertyRelocator( true, true, "Property store relocation" );


private final boolean parallel; private final boolean parallel;
private final boolean forward; private final boolean forward;
Expand Down
Expand Up @@ -21,6 +21,8 @@


import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;


import org.neo4j.consistency.RecordType; import org.neo4j.consistency.RecordType;
import org.neo4j.consistency.checking.NodeRecordCheck; import org.neo4j.consistency.checking.NodeRecordCheck;
Expand Down Expand Up @@ -48,6 +50,7 @@
import org.neo4j.kernel.impl.store.SchemaStorage; import org.neo4j.kernel.impl.store.SchemaStorage;
import org.neo4j.kernel.impl.store.StoreAccess; import org.neo4j.kernel.impl.store.StoreAccess;
import org.neo4j.kernel.impl.store.record.AbstractBaseRecord; import org.neo4j.kernel.impl.store.record.AbstractBaseRecord;
import org.neo4j.storageengine.api.EntityType;
import org.neo4j.storageengine.api.schema.StoreIndexDescriptor; import org.neo4j.storageengine.api.schema.StoreIndexDescriptor;


import static java.lang.String.format; import static java.lang.String.format;
Expand Down Expand Up @@ -138,14 +141,28 @@ public List<ConsistencyCheckerTask> createTasksForFullCheck( boolean checkLabelS
tasks.add( create( "RelationshipGroupStore-RelGrp", nativeStores.getRelationshipGroupStore(), tasks.add( create( "RelationshipGroupStore-RelGrp", nativeStores.getRelationshipGroupStore(),
relGrpProcessor, ROUND_ROBIN ) ); relGrpProcessor, ROUND_ROBIN ) );


NodePropertyReader propertyReader = new NodePropertyReader( nativeStores ); PropertyReader propertyReader = new PropertyReader( nativeStores );
tasks.add( recordScanner( CheckStage.Stage8_PS_Props.name(), tasks.add( recordScanner( CheckStage.Stage8_PS_Props.name(),
new IterableStore<>( nativeStores.getNodeStore(), true ), new IterableStore<>( nativeStores.getNodeStore(), true ),
new PropertyAndNode2LabelIndexProcessor( reporter, checkIndexes ? indexes : null, new PropertyAndNode2LabelIndexProcessor( reporter, checkIndexes ? indexes : null,
propertyReader, cacheAccess, mandatoryProperties.forNodes( reporter ) ), propertyReader, cacheAccess, mandatoryProperties.forNodes( reporter ) ),
CheckStage.Stage8_PS_Props, ROUND_ROBIN, CheckStage.Stage8_PS_Props, ROUND_ROBIN,
new IterableStore<>( nativeStores.getPropertyStore(), true ) ) ); new IterableStore<>( nativeStores.getPropertyStore(), true ) ) );


// Checking that relationships are in their expected relationship indexes.
List<StoreIndexDescriptor> relationshipIndexes = StreamSupport.stream( indexes.onlineRules().spliterator(), false )
.filter( rule -> rule.schema().entityType() == EntityType.RELATIONSHIP )
.collect( Collectors.toList() );
if ( checkIndexes && !relationshipIndexes.isEmpty() )
{
tasks.add( recordScanner( CheckStage.Stage9_RS_Indexes.name(),
new IterableStore<>( nativeStores.getRelationshipStore(), true ),
new RelationshipIndexProcessor( reporter, indexes, propertyReader, cacheAccess, relationshipIndexes ),
CheckStage.Stage9_RS_Indexes,
ROUND_ROBIN,
new IterableStore<>( nativeStores.getPropertyStore(), true ) ) );
}

tasks.add( create( "StringStore-Str", nativeStores.getStringStore(), tasks.add( create( "StringStore-Str", nativeStores.getStringStore(),
multiPass.processor( Stage.SEQUENTIAL_FORWARD, STRINGS ), ROUND_ROBIN ) ); multiPass.processor( Stage.SEQUENTIAL_FORWARD, STRINGS ), ROUND_ROBIN ) );
tasks.add( create( "ArrayStore-Arrays", nativeStores.getArrayStore(), tasks.add( create( "ArrayStore-Arrays", nativeStores.getArrayStore(),
Expand Down
Expand Up @@ -46,7 +46,7 @@ public class PropertyAndNode2LabelIndexProcessor extends RecordProcessor.Adapter


public PropertyAndNode2LabelIndexProcessor( ConsistencyReporter reporter, public PropertyAndNode2LabelIndexProcessor( ConsistencyReporter reporter,
IndexAccessors indexes, IndexAccessors indexes,
NodePropertyReader propertyReader, PropertyReader propertyReader,
CacheAccess cacheAccess, CacheAccess cacheAccess,
Function<NodeRecord,MandatoryProperties.Check<NodeRecord,ConsistencyReport.NodeConsistencyReport>> mandatoryProperties ) Function<NodeRecord,MandatoryProperties.Check<NodeRecord,ConsistencyReport.NodeConsistencyReport>> mandatoryProperties )
{ {
Expand Down
Expand Up @@ -29,8 +29,6 @@
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;


import org.neo4j.consistency.checking.ChainCheck; import org.neo4j.consistency.checking.ChainCheck;
import org.neo4j.consistency.checking.CheckerEngine; import org.neo4j.consistency.checking.CheckerEngine;
Expand Down Expand Up @@ -61,10 +59,10 @@
public class PropertyAndNodeIndexedCheck implements RecordCheck<NodeRecord, ConsistencyReport.NodeConsistencyReport> public class PropertyAndNodeIndexedCheck implements RecordCheck<NodeRecord, ConsistencyReport.NodeConsistencyReport>
{ {
private final IndexAccessors indexes; private final IndexAccessors indexes;
private final NodePropertyReader propertyReader; private final PropertyReader propertyReader;
private final CacheAccess cacheAccess; private final CacheAccess cacheAccess;


public PropertyAndNodeIndexedCheck( IndexAccessors indexes, NodePropertyReader propertyReader, CacheAccess cacheAccess ) public PropertyAndNodeIndexedCheck( IndexAccessors indexes, PropertyReader propertyReader, CacheAccess cacheAccess )
{ {
this.indexes = indexes; this.indexes = indexes;
this.propertyReader = propertyReader; this.propertyReader = propertyReader;
Expand Down Expand Up @@ -108,9 +106,10 @@ private void matchIndexesToNode(
} }


int[] indexPropertyIds = schema.getPropertyIds(); int[] indexPropertyIds = schema.getPropertyIds();
if ( nodeHasSchemaProperties( nodePropertyMap, indexPropertyIds ) ) boolean requireAllProperties = schema.propertySchemaType() == SchemaDescriptor.PropertySchemaType.COMPLETE_ALL_TOKENS;
if ( requireAllProperties ? hasAllProperties( nodePropertyMap, indexPropertyIds ) : hasAnyProperty( nodePropertyMap, indexPropertyIds ) )
{ {
Value[] values = getPropertyValues( nodePropertyMap, indexPropertyIds ); Value[] values = getPropertyValues( propertyReader, nodePropertyMap, indexPropertyIds );
try ( IndexReader reader = indexes.accessorFor( indexRule ).newReader() ) try ( IndexReader reader = indexes.accessorFor( indexRule ).newReader() )
{ {
long nodeId = record.getId(); long nodeId = record.getId();
Expand Down Expand Up @@ -204,7 +203,7 @@ private void checkProperty( NodeRecord record,
} }
} }


private Value[] getPropertyValues( IntObjectMap<PropertyBlock> propertyMap, int[] indexPropertyIds ) static Value[] getPropertyValues( PropertyReader propertyReader, IntObjectMap<PropertyBlock> propertyMap, int[] indexPropertyIds )
{ {
Value[] values = new Value[indexPropertyIds.length]; Value[] values = new Value[indexPropertyIds.length];
for ( int i = 0; i < indexPropertyIds.length; i++ ) for ( int i = 0; i < indexPropertyIds.length; i++ )
Expand All @@ -215,7 +214,7 @@ private Value[] getPropertyValues( IntObjectMap<PropertyBlock> propertyMap, int[
return values; return values;
} }


private IntObjectMap<PropertyBlock> properties( List<PropertyBlock> propertyBlocks ) static IntObjectMap<PropertyBlock> properties( List<PropertyBlock> propertyBlocks )
{ {
final MutableIntObjectMap<PropertyBlock> propertyIds = new IntObjectHashMap<>(); final MutableIntObjectMap<PropertyBlock> propertyIds = new IntObjectHashMap<>();
for ( PropertyBlock propertyBlock : propertyBlocks ) for ( PropertyBlock propertyBlock : propertyBlocks )
Expand Down Expand Up @@ -255,16 +254,27 @@ private LongIterator queryIndexOrEmpty( IndexReader reader, IndexQuery[] query )
? indexedNodeIds : LookupFilter.exactIndexMatches( propertyReader, indexedNodeIds, query ); ? indexedNodeIds : LookupFilter.exactIndexMatches( propertyReader, indexedNodeIds, query );
} }


private static boolean nodeHasSchemaProperties( static boolean hasAllProperties( IntObjectMap<PropertyBlock> blockMap, int[] indexPropertyIds )
IntObjectMap<PropertyBlock> nodePropertyMap, int[] indexPropertyIds )
{ {
for ( int indexPropertyId : indexPropertyIds ) for ( int indexPropertyId : indexPropertyIds )
{ {
if ( !nodePropertyMap.containsKey( indexPropertyId ) ) if ( !blockMap.containsKey( indexPropertyId ) )
{ {
return false; return false;
} }
} }
return true; return true;
} }

static boolean hasAnyProperty( IntObjectMap<PropertyBlock> blockMap, int[] indexPropertyIds )
{
for ( int indexPropertyId : indexPropertyIds )
{
if ( blockMap.containsKey( indexPropertyId ) )
{
return true;
}
}
return false;
}
} }
Expand Up @@ -27,33 +27,38 @@
import org.neo4j.kernel.api.index.NodePropertyAccessor; import org.neo4j.kernel.api.index.NodePropertyAccessor;
import org.neo4j.kernel.impl.store.NodeStore; import org.neo4j.kernel.impl.store.NodeStore;
import org.neo4j.kernel.impl.store.PropertyStore; import org.neo4j.kernel.impl.store.PropertyStore;
import org.neo4j.kernel.impl.store.RelationshipStore;
import org.neo4j.kernel.impl.store.StoreAccess; import org.neo4j.kernel.impl.store.StoreAccess;
import org.neo4j.kernel.impl.store.record.NodeRecord; import org.neo4j.kernel.impl.store.record.NodeRecord;
import org.neo4j.kernel.impl.store.record.PrimitiveRecord;
import org.neo4j.kernel.impl.store.record.PropertyBlock; import org.neo4j.kernel.impl.store.record.PropertyBlock;
import org.neo4j.kernel.impl.store.record.PropertyRecord; import org.neo4j.kernel.impl.store.record.PropertyRecord;
import org.neo4j.kernel.impl.store.record.Record; import org.neo4j.kernel.impl.store.record.Record;
import org.neo4j.kernel.impl.store.record.RelationshipRecord;
import org.neo4j.values.storable.Value; import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.Values; import org.neo4j.values.storable.Values;


import static org.neo4j.kernel.impl.store.record.RecordLoad.FORCE; import static org.neo4j.kernel.impl.store.record.RecordLoad.FORCE;


public class NodePropertyReader implements NodePropertyAccessor public class PropertyReader implements NodePropertyAccessor
{ {
private final PropertyStore propertyStore; private final PropertyStore propertyStore;
private final NodeStore nodeStore; private final NodeStore nodeStore;
private final RelationshipStore relationshipStore;


public NodePropertyReader( StoreAccess storeAccess ) PropertyReader( StoreAccess storeAccess )
{ {
this.propertyStore = storeAccess.getRawNeoStores().getPropertyStore(); this.propertyStore = storeAccess.getRawNeoStores().getPropertyStore();
this.nodeStore = storeAccess.getRawNeoStores().getNodeStore(); this.nodeStore = storeAccess.getRawNeoStores().getNodeStore();
this.relationshipStore = storeAccess.getRawNeoStores().getRelationshipStore();
} }


public Collection<PropertyRecord> getPropertyRecordChain( NodeRecord nodeRecord ) Collection<PropertyRecord> getPropertyRecordChain( PrimitiveRecord entityRecord )
{ {
return getPropertyRecordChain( nodeRecord.getNextProp() ); return getPropertyRecordChain( entityRecord.getNextProp() );
} }


public Collection<PropertyRecord> getPropertyRecordChain( long firstId ) private Collection<PropertyRecord> getPropertyRecordChain( long firstId )
{ {
long nextProp = firstId; long nextProp = firstId;
List<PropertyRecord> toReturn = new LinkedList<>(); List<PropertyRecord> toReturn = new LinkedList<>();
Expand All @@ -66,7 +71,7 @@ public Collection<PropertyRecord> getPropertyRecordChain( long firstId )
return toReturn; return toReturn;
} }


public List<PropertyBlock> propertyBlocks( Collection<PropertyRecord> records ) List<PropertyBlock> propertyBlocks( Collection<PropertyRecord> records )
{ {
List<PropertyBlock> propertyBlocks = new ArrayList<>(); List<PropertyBlock> propertyBlocks = new ArrayList<>();
for ( PropertyRecord record : records ) for ( PropertyRecord record : records )
Expand All @@ -79,9 +84,9 @@ public List<PropertyBlock> propertyBlocks( Collection<PropertyRecord> records )
return propertyBlocks; return propertyBlocks;
} }


public List<PropertyBlock> propertyBlocks( NodeRecord nodeRecord ) private List<PropertyBlock> propertyBlocks( PrimitiveRecord entityRecord )
{ {
Collection<PropertyRecord> records = propertyStore.getPropertyRecordChain( nodeRecord.getNextProp() ); Collection<PropertyRecord> records = propertyStore.getPropertyRecordChain( entityRecord.getNextProp() );
List<PropertyBlock> propertyBlocks = new ArrayList<>(); List<PropertyBlock> propertyBlocks = new ArrayList<>();
for ( PropertyRecord record : records ) for ( PropertyRecord record : records )
{ {
Expand Down Expand Up @@ -114,4 +119,20 @@ public Value getNodePropertyValue( long nodeId, int propertyKeyId )
} }
return Values.NO_VALUE; return Values.NO_VALUE;
} }

public Value getRelationshipPropertyValue( long relId, int propertyKeyId )
{
RelationshipRecord record = relationshipStore.newRecord();
if ( relationshipStore.getRecord( relId, record, FORCE ).inUse() )
{
for ( PropertyBlock block : propertyBlocks( record ) )
{
if ( block.getKeyIndexId() == propertyKeyId )
{
return propertyValue( block );
}
}
}
return Values.NO_VALUE;
}
} }
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2002-2018 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.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.consistency.checking.full;

import java.util.List;

import org.neo4j.consistency.checking.cache.CacheAccess;
import org.neo4j.consistency.checking.index.IndexAccessors;
import org.neo4j.consistency.report.ConsistencyReporter;
import org.neo4j.kernel.impl.store.record.RelationshipRecord;
import org.neo4j.storageengine.api.schema.StoreIndexDescriptor;

public class RelationshipIndexProcessor extends RecordProcessor.Adapter<RelationshipRecord>
{
private final ConsistencyReporter reporter;
private final RelationshipToIndexCheck checker;

RelationshipIndexProcessor( ConsistencyReporter reporter, IndexAccessors indexes, PropertyReader propertyReader, CacheAccess cacheAccess,
List<StoreIndexDescriptor> relationshipIndexes )
{
this.reporter = reporter;
checker = new RelationshipToIndexCheck( relationshipIndexes, indexes, propertyReader, cacheAccess );
}

@Override
public void process( RelationshipRecord relationshipRecord )
{
reporter.forRelationship( relationshipRecord, checker );
}
}
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2002-2018 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.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.consistency.checking.full;

import org.apache.commons.lang3.ArrayUtils;
import org.eclipse.collections.api.map.primitive.IntObjectMap;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.neo4j.consistency.checking.CheckerEngine;
import org.neo4j.consistency.checking.RecordCheck;
import org.neo4j.consistency.checking.cache.CacheAccess;
import org.neo4j.consistency.checking.index.IndexAccessors;
import org.neo4j.consistency.report.ConsistencyReport;
import org.neo4j.consistency.store.RecordAccess;
import org.neo4j.internal.kernel.api.schema.SchemaDescriptor;
import org.neo4j.kernel.impl.store.record.PropertyBlock;
import org.neo4j.kernel.impl.store.record.PropertyRecord;
import org.neo4j.kernel.impl.store.record.RelationshipRecord;
import org.neo4j.storageengine.api.schema.IndexReader;
import org.neo4j.storageengine.api.schema.StoreIndexDescriptor;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.Values;

import static org.neo4j.consistency.checking.full.PropertyAndNodeIndexedCheck.getPropertyValues;
import static org.neo4j.consistency.checking.full.PropertyAndNodeIndexedCheck.hasAllProperties;
import static org.neo4j.consistency.checking.full.PropertyAndNodeIndexedCheck.hasAnyProperty;
import static org.neo4j.consistency.checking.full.PropertyAndNodeIndexedCheck.properties;

public class RelationshipToIndexCheck implements RecordCheck<RelationshipRecord, ConsistencyReport.RelationshipConsistencyReport>
{
private final IndexAccessors indexes;
private final StoreIndexDescriptor[] relationshipIndexes;
private final PropertyReader propertyReader;

RelationshipToIndexCheck( List<StoreIndexDescriptor> relationshipIndexes, IndexAccessors indexes, PropertyReader propertyReader, CacheAccess cacheAccess )
{
this.relationshipIndexes = relationshipIndexes.toArray( new StoreIndexDescriptor[0] );
this.indexes = indexes;
this.propertyReader = propertyReader;
}

@Override
public void check( RelationshipRecord record, CheckerEngine<RelationshipRecord,ConsistencyReport.RelationshipConsistencyReport> engine,
RecordAccess records )
{
IntObjectMap<PropertyBlock> nodePropertyMap = null;
for ( StoreIndexDescriptor index : relationshipIndexes )
{
SchemaDescriptor schema = index.schema();
if ( ArrayUtils.contains( schema.getEntityTokenIds(), record.getType() ) )
{
if ( nodePropertyMap == null )
{
Collection<PropertyRecord> propertyRecs = propertyReader.getPropertyRecordChain( record );
nodePropertyMap = properties( propertyReader.propertyBlocks( propertyRecs ) );
}

int[] indexPropertyIds = schema.getPropertyIds();
boolean requireAllProperties = schema.propertySchemaType() == SchemaDescriptor.PropertySchemaType.COMPLETE_ALL_TOKENS;
if ( requireAllProperties ? hasAllProperties( nodePropertyMap, indexPropertyIds ) : hasAnyProperty( nodePropertyMap, indexPropertyIds ) )
{
Value[] values = getPropertyValues( propertyReader, nodePropertyMap, indexPropertyIds );
try ( IndexReader reader = indexes.accessorFor( index ).newReader() )
{
assert !index.canSupportUniqueConstraint();
long entityId = record.getId();
long count = reader.countIndexedNodes( entityId, indexPropertyIds, values );
reportIncorrectIndexCount( values, engine, index, count );
}
}
}
}
}

private void reportIncorrectIndexCount( Value[] values, CheckerEngine<RelationshipRecord,ConsistencyReport.RelationshipConsistencyReport> engine,
StoreIndexDescriptor index, long count )
{
if ( count == 0 )
{
engine.report().notIndexed( index, Values.asObjects( values ) );
}
else if ( count != 1 )
{
engine.report().indexedMultipleTimes( index, Values.asObjects( values ), count );
}
}
}

0 comments on commit c6a9755

Please sign in to comment.