/** * Copyright (c) 2002-2012 "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 Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.consistency.checking; import java.util.HashMap; import java.util.Map; import org.neo4j.consistency.report.ConsistencyReport; import org.neo4j.consistency.store.DiffRecordAccess; import org.neo4j.consistency.store.RecordAccess; import org.neo4j.kernel.impl.nioneo.store.DynamicRecord; import org.neo4j.kernel.impl.nioneo.store.PropertyBlock; import org.neo4j.kernel.impl.nioneo.store.PropertyIndexRecord; import org.neo4j.kernel.impl.nioneo.store.PropertyRecord; import org.neo4j.kernel.impl.nioneo.store.PropertyType; import org.neo4j.kernel.impl.nioneo.store.Record; class PropertyRecordCheck implements RecordCheck<PropertyRecord, ConsistencyReport.PropertyConsistencyReport> { @Override public void checkChange( PropertyRecord oldRecord, PropertyRecord newRecord, ConsistencyReport.PropertyConsistencyReport report, DiffRecordAccess records ) { check( newRecord, report, records ); if ( oldRecord.inUse() ) { for ( PropertyField field : PropertyField.values() ) { field.checkChange( oldRecord, newRecord, report, records ); } } // Did this record belong to the marked owner before it changed? - does it belong to it now? if ( oldRecord.inUse() ) { OwnerChain.OLD.check( newRecord, report, records ); } if ( newRecord.inUse() ) { OwnerChain.NEW.check( newRecord, report, records ); } // Previously referenced dynamic records should either still be referenced, or be deleted Map<Long, PropertyBlock> prevStrings = new HashMap<Long, PropertyBlock>(); Map<Long, PropertyBlock> prevArrays = new HashMap<Long, PropertyBlock>(); for ( PropertyBlock block : oldRecord.getPropertyBlocks() ) { PropertyType type = block.getType(); if ( type != null ) { switch ( type ) { case STRING: prevStrings.put( block.getSingleValueLong(), block ); break; case ARRAY: prevArrays.put( block.getSingleValueLong(), block ); break; } } } for ( PropertyBlock block : newRecord.getPropertyBlocks() ) { PropertyType type = block.getType(); if ( type != null ) { switch ( type ) { case STRING: prevStrings.remove( block.getSingleValueLong() ); break; case ARRAY: prevArrays.remove( block.getSingleValueLong() ); break; } } } for ( PropertyBlock block : prevStrings.values() ) { if ( records.changedString( block.getSingleValueLong() ) == null ) { report.stringUnreferencedButNotDeleted( block ); } } for ( PropertyBlock block : prevArrays.values() ) { if ( records.changedArray( block.getSingleValueLong() ) == null ) { report.arrayUnreferencedButNotDeleted( block ); } } } @Override public void check( PropertyRecord record, ConsistencyReport.PropertyConsistencyReport report, RecordAccess records ) { if ( !record.inUse() ) { return; } for ( PropertyField field : PropertyField.values() ) { field.checkConsistency( record, report, records ); } for ( PropertyBlock block : record.getPropertyBlocks() ) { checkDataBlock( block, report, records ); } } private void checkDataBlock( PropertyBlock block, ConsistencyReport.PropertyConsistencyReport report, RecordAccess records ) { if ( block.getKeyIndexId() < 0 ) { report.invalidPropertyKey( block ); } else { report.forReference( records.propertyKey( block.getKeyIndexId() ), propertyKey( block ) ); } PropertyType type = block.forceGetType(); if ( type == null ) { report.invalidPropertyType( block ); } else { switch ( type ) { case STRING: report.forReference( records.string( block.getSingleValueLong() ), DynamicReference.string( block ) ); break; case ARRAY: report.forReference( records.array( block.getSingleValueLong() ), DynamicReference.array( block ) ); break; default: try { type.getValue( block, null ); } catch ( Exception e ) { report.invalidPropertyValue( block ); } break; } } } private enum PropertyField implements RecordField<PropertyRecord, ConsistencyReport.PropertyConsistencyReport>, ComparativeRecordChecker<PropertyRecord, PropertyRecord, ConsistencyReport.PropertyConsistencyReport> { PREV( Record.NO_PREVIOUS_PROPERTY ) { @Override public long valueFrom( PropertyRecord record ) { return record.getPrevProp(); } @Override long otherReference( PropertyRecord record ) { return record.getNextProp(); } @Override void notInUse( ConsistencyReport.PropertyConsistencyReport report, PropertyRecord property ) { report.prevNotInUse( property ); } @Override void noBackReference( ConsistencyReport.PropertyConsistencyReport report, PropertyRecord property ) { report.previousDoesNotReferenceBack( property ); } @Override void reportNotUpdated( ConsistencyReport.PropertyConsistencyReport report ) { report.prevNotUpdated(); } }, NEXT( Record.NO_NEXT_PROPERTY ) { @Override public long valueFrom( PropertyRecord record ) { return record.getNextProp(); } @Override long otherReference( PropertyRecord record ) { return record.getPrevProp(); } @Override void notInUse( ConsistencyReport.PropertyConsistencyReport report, PropertyRecord property ) { report.nextNotInUse( property ); } @Override void noBackReference( ConsistencyReport.PropertyConsistencyReport report, PropertyRecord property ) { report.nextDoesNotReferenceBack( property ); } @Override void reportNotUpdated( ConsistencyReport.PropertyConsistencyReport report ) { report.nextNotUpdated(); } }; private final Record NONE; private PropertyField( Record none ) { this.NONE = none; } abstract long otherReference( PropertyRecord record ); @Override public void checkConsistency( PropertyRecord record, ConsistencyReport.PropertyConsistencyReport report, RecordAccess records ) { if ( !NONE.is( valueFrom( record ) ) ) { report.forReference( records.property( valueFrom( record ) ), this ); } } @Override public void checkReference( PropertyRecord record, PropertyRecord referred, ConsistencyReport.PropertyConsistencyReport report, RecordAccess records ) { if ( !referred.inUse() ) { notInUse( report, referred ); } else { if ( otherReference( referred ) != record.getId() ) { noBackReference( report, referred ); } } } @Override public void checkChange( PropertyRecord oldRecord, PropertyRecord newRecord, ConsistencyReport.PropertyConsistencyReport report, DiffRecordAccess records ) { if ( !newRecord.inUse() || valueFrom( oldRecord ) != valueFrom( newRecord ) ) { if ( !NONE.is( valueFrom( oldRecord ) ) && records.changedProperty( valueFrom( oldRecord ) ) == null ) { reportNotUpdated(report); } } } abstract void reportNotUpdated( ConsistencyReport.PropertyConsistencyReport report ); abstract void notInUse( ConsistencyReport.PropertyConsistencyReport report, PropertyRecord property ); abstract void noBackReference( ConsistencyReport.PropertyConsistencyReport report, PropertyRecord property ); } private static ComparativeRecordChecker<PropertyRecord, PropertyIndexRecord, ConsistencyReport.PropertyConsistencyReport> propertyKey( final PropertyBlock block ) { return new ComparativeRecordChecker<PropertyRecord, PropertyIndexRecord, ConsistencyReport.PropertyConsistencyReport>() { @Override public void checkReference( PropertyRecord record, PropertyIndexRecord referred, ConsistencyReport.PropertyConsistencyReport report, RecordAccess records ) { if ( !referred.inUse() ) { report.keyNotInUse( block, referred ); } } @Override public String toString() { return "PROPERTY_KEY"; } }; } private static abstract class DynamicReference implements ComparativeRecordChecker<PropertyRecord, DynamicRecord, ConsistencyReport.PropertyConsistencyReport> { final PropertyBlock block; private DynamicReference( PropertyBlock block ) { this.block = block; } public static DynamicReference string( PropertyBlock block ) { return new DynamicReference( block ) { @Override void notUsed( ConsistencyReport.PropertyConsistencyReport report, DynamicRecord value ) { report.stringNotInUse( block, value ); } @Override void empty( ConsistencyReport.PropertyConsistencyReport report, DynamicRecord value ) { report.stringEmpty( block, value ); } }; } public static DynamicReference array( PropertyBlock block ) { return new DynamicReference( block ) { @Override void notUsed( ConsistencyReport.PropertyConsistencyReport report, DynamicRecord value ) { report.arrayNotInUse( block, value ); } @Override void empty( ConsistencyReport.PropertyConsistencyReport report, DynamicRecord value ) { report.arrayEmpty( block, value ); } }; } @Override public void checkReference( PropertyRecord record, DynamicRecord referred, ConsistencyReport.PropertyConsistencyReport report, RecordAccess records ) { if ( !referred.inUse() ) { notUsed( report, referred ); } else { if ( referred.getLength() <= 0 ) { empty( report, referred ); } } } abstract void notUsed( ConsistencyReport.PropertyConsistencyReport report, DynamicRecord value ); abstract void empty( ConsistencyReport.PropertyConsistencyReport report, DynamicRecord value ); } }