All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.consistency.checker.RelationshipGroupChecker Maven / Gradle / Ivy

There is a newer version: 5.26.1
Show newest version
/*
 * Copyright (c) "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 .
 */
package org.neo4j.consistency.checker;

import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.neo4j.consistency.checking.cache.CacheAccess;
import org.neo4j.consistency.checking.cache.CacheSlots;
import org.neo4j.consistency.checking.full.ConsistencyFlags;
import org.neo4j.consistency.report.ConsistencyReport;
import org.neo4j.internal.helpers.collection.LongRange;
import org.neo4j.internal.helpers.progress.ProgressListener;
import org.neo4j.internal.recordstorage.RecordRelationshipScanCursor;
import org.neo4j.internal.recordstorage.RecordStorageReader;
import org.neo4j.io.pagecache.context.CursorContext;
import org.neo4j.io.pagecache.tracing.PageCacheTracer;
import org.neo4j.kernel.impl.store.NeoStores;
import org.neo4j.kernel.impl.store.RelationshipGroupStore;
import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord;
import org.neo4j.kernel.impl.store.record.RelationshipRecord;

import static org.neo4j.consistency.checker.RecordLoading.checkValidToken;
import static org.neo4j.kernel.impl.store.record.Record.NULL_REFERENCE;

/**
 * Checks relationship groups vs the relationships and node refer to.
 */
class RelationshipGroupChecker implements Checker
{
    private static final String RELATIONSHIP_GROUPS_CHECKER_TAG = "relationshipGroupsChecker";
    private final NeoStores neoStores;
    private final ConsistencyReport.Reporter reporter;
    private final CheckerContext context;
    private final ProgressListener progress;

    RelationshipGroupChecker( CheckerContext context )
    {
        this.neoStores = context.neoStores;
        this.reporter = context.reporter;
        this.context = context;
        this.progress = context.progressReporter( this, "Relationship groups", neoStores.getRelationshipGroupStore().getHighId() );
    }

    @Override
    public void check( LongRange nodeIdRange, boolean firstRange, boolean lastRange ) throws Exception
    {
        ParallelExecution execution = context.execution;
        checkToOwner( nodeIdRange, context.pageCacheTracer );
        if ( firstRange )
        {
            execution.run( getClass().getSimpleName(), execution.partition( neoStores.getRelationshipGroupStore(),
                    ( from, to, last ) -> () -> checkToRelationship( from, to, context.pageCacheTracer ) ) );
        }
    }

    @Override
    public boolean shouldBeChecked( ConsistencyFlags flags )
    {
        return flags.isCheckGraph();
    }

    /**
     * Check relationship group to owner node
     */
    private void checkToOwner( LongRange nodeIdRange, PageCacheTracer pageCacheTracer )
    {
        ProgressListener localProgress = progress.threadLocalReporter();
        RelationshipGroupStore groupStore = neoStores.getRelationshipGroupStore();
        CacheAccess.Client client = context.cacheAccess.client();
        final long highId = groupStore.getHighId();

        try ( var cursorContext = new CursorContext( pageCacheTracer.createPageCursorTracer( RELATIONSHIP_GROUPS_CHECKER_TAG ) );
                RecordReader groupReader = new RecordReader<>( neoStores.getRelationshipGroupStore(), true, cursorContext ) )
        {
            for ( long id = 0; id < highId && !context.isCancelled(); id++ )
            {
                localProgress.add( 1 );
                RelationshipGroupRecord record = groupReader.read( id );
                if ( !record.inUse() )
                {
                    continue;
                }

                long owningNode = record.getOwningNode();
                if ( nodeIdRange.isWithinRangeExclusiveTo( owningNode ) )
                {
                    long cachedOwnerNextRel = client.getFromCache( owningNode, CacheSlots.NodeLink.SLOT_RELATIONSHIP_ID );
                    boolean nodeIsInUse = client.getBooleanFromCache( owningNode, CacheSlots.NodeLink.SLOT_IN_USE );
                    if ( !nodeIsInUse )
                    {
                        reporter.forRelationshipGroup( record ).ownerNotInUse();
                    }
                    else if ( cachedOwnerNextRel == id )
                    {
                        // The old checker only verified that the relationship group that node.nextGroup pointed to had this node as its owner
                        client.putToCacheSingle( owningNode, CacheSlots.NodeLink.SLOT_CHECK_MARK, 0 );
                    }

                    if ( NULL_REFERENCE.is( record.getNext() ) )
                    {
                        // This is the last group in the chain for this node. Verify that there's only one such last group.
                        boolean hasAlreadySeenLastGroup = client.getBooleanFromCache( owningNode, CacheSlots.NodeLink.SLOT_HAS_LAST_GROUP );
                        if ( hasAlreadySeenLastGroup )
                        {
                            reporter.forRelationshipGroup( record ).multipleLastGroups( context.recordLoader.node( owningNode, cursorContext ) );
                        }
                        client.putToCacheSingle( owningNode, CacheSlots.NodeLink.SLOT_HAS_LAST_GROUP, 1 );
                    }
                }
            }
        }
        localProgress.done();
    }

    /**
     * Check relationship groups to first in chain relationship. Run only on first node-range
     */
    private void checkToRelationship( long fromGroupId, long toGroupId, PageCacheTracer pageCacheTracer )
    {
        try ( var cursorContext = new CursorContext( pageCacheTracer.createPageCursorTracer( RELATIONSHIP_GROUPS_CHECKER_TAG ) );
              RecordReader groupReader = new RecordReader<>( neoStores.getRelationshipGroupStore(), true, cursorContext );
              RecordReader comparativeReader = new RecordReader<>( neoStores.getRelationshipGroupStore(), false, cursorContext );
              RecordStorageReader reader = new RecordStorageReader( neoStores );
              RecordRelationshipScanCursor relationshipCursor = reader.allocateRelationshipScanCursor( cursorContext ) )
        {
            for ( long id = fromGroupId; id < toGroupId && !context.isCancelled(); id++ )
            {
                RelationshipGroupRecord record = groupReader.read( id );
                if ( !record.inUse() )
                {
                    continue;
                }

                long owningNode = record.getOwningNode();

                if ( owningNode < 0 )
                {
                    reporter.forRelationshipGroup( record ).illegalOwner();
                }
                checkValidToken( record, record.getType(), context.tokenHolders.relationshipTypeTokens(), neoStores.getRelationshipTypeTokenStore(),
                        ( group, token ) -> reporter.forRelationshipGroup( group ).illegalRelationshipType(),
                        ( group, token ) -> reporter.forRelationshipGroup( group ).relationshipTypeNotInUse( token ), cursorContext );

                if ( !NULL_REFERENCE.is( record.getNext() ) )
                {
                    RelationshipGroupRecord comparativeRecord = comparativeReader.read( record.getNext() );
                    if ( !comparativeRecord.inUse() )
                    {
                        reporter.forRelationshipGroup( record ).nextGroupNotInUse();
                    }
                    else
                    {
                        if ( record.getType() >= comparativeRecord.getType() )
                        {
                            reporter.forRelationshipGroup( record ).invalidTypeSortOrder();
                        }
                        if ( owningNode != comparativeRecord.getOwningNode() )
                        {
                            reporter.forRelationshipGroup( record ).nextHasOtherOwner( comparativeRecord );
                        }
                    }
                }

                checkRelationshipGroupRelationshipLink( relationshipCursor, record, record.getFirstOut(), RelationshipGroupLink.OUT,
                        group -> reporter.forRelationshipGroup( group ).firstOutgoingRelationshipNotInUse(),
                        group -> reporter.forRelationshipGroup( group ).firstOutgoingRelationshipNotFirstInChain(),
                        group -> reporter.forRelationshipGroup( group ).firstOutgoingRelationshipOfOtherType(),
                        ( group, rel ) -> reporter.forRelationshipGroup( group ).firstOutgoingRelationshipDoesNotShareNodeWithGroup( rel ),
                        cursorContext );
                checkRelationshipGroupRelationshipLink( relationshipCursor, record, record.getFirstIn(), RelationshipGroupLink.IN,
                        group -> reporter.forRelationshipGroup( group ).firstIncomingRelationshipNotInUse(),
                        group -> reporter.forRelationshipGroup( group ).firstIncomingRelationshipNotFirstInChain(),
                        group -> reporter.forRelationshipGroup( group ).firstIncomingRelationshipOfOtherType(),
                        ( group, rel ) -> reporter.forRelationshipGroup( group ).firstIncomingRelationshipDoesNotShareNodeWithGroup( rel ),
                        cursorContext );
                checkRelationshipGroupRelationshipLink( relationshipCursor, record, record.getFirstLoop(), RelationshipGroupLink.LOOP,
                        group -> reporter.forRelationshipGroup( group ).firstLoopRelationshipNotInUse(),
                        group -> reporter.forRelationshipGroup( group ).firstLoopRelationshipNotFirstInChain(),
                        group -> reporter.forRelationshipGroup( group ).firstLoopRelationshipOfOtherType(),
                        ( group, rel ) -> reporter.forRelationshipGroup( group ).firstLoopRelationshipDoesNotShareNodeWithGroup( rel ),
                        cursorContext );
            }

        }
    }

    private void checkRelationshipGroupRelationshipLink( RecordRelationshipScanCursor relationshipCursor, RelationshipGroupRecord record, long relationshipId,
            RelationshipGroupLink relationshipGroupLink, Consumer reportRelationshipNotInUse,
            Consumer reportRelationshipNotFirstInChain, Consumer reportRelationshipOfOtherType,
            BiConsumer reportNodeNotSharedWithGroup, CursorContext cursorContext )
    {
        if ( !NULL_REFERENCE.is( relationshipId ) )
        {
            relationshipCursor.single( relationshipId );
            if ( !relationshipCursor.next() )
            {
                reportRelationshipNotInUse.accept( record );
            }
            else
            {
                if ( !relationshipGroupLink.isFirstInChain( relationshipCursor ) )
                {
                    reportRelationshipNotFirstInChain.accept( record );
                }
                if ( relationshipCursor.getType() != record.getType() )
                {
                    reportRelationshipOfOtherType.accept( record );
                }

                boolean hasCorrectNode =
                        relationshipCursor.getFirstNode() == record.getOwningNode() || relationshipCursor.getSecondNode() == record.getOwningNode();
                if ( !hasCorrectNode )
                {
                    reportNodeNotSharedWithGroup.accept( record, context.recordLoader.relationship( relationshipCursor.getId(), cursorContext ) );
                }
            }
        }
    }

    @Override
    public String toString()
    {
        return String.format( "%s[highId:%d]", getClass().getSimpleName(), neoStores.getRelationshipGroupStore().getHighId() );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy