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

org.neo4j.kernel.impl.newapi.DefaultEntityValueIndexCursor Maven / Gradle / Ivy

Go to download

Neo4j kernel is a lightweight, embedded Java database designed to store data structured as graphs rather than tables. For more information, see http://neo4j.org.

There is a newer version: 5.25.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.kernel.impl.newapi;

import org.eclipse.collections.api.iterator.LongIterator;
import org.eclipse.collections.api.set.primitive.ImmutableLongSet;
import org.eclipse.collections.api.set.primitive.LongSet;
import org.eclipse.collections.api.tuple.primitive.LongObjectPair;
import org.eclipse.collections.impl.factory.primitive.LongSets;
import org.eclipse.collections.impl.iterator.ImmutableEmptyLongIterator;
import org.eclipse.collections.impl.tuple.primitive.PrimitiveTuples;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.neo4j.collection.trackable.HeapTrackingArrayList;
import org.neo4j.graphdb.Resource;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.internal.helpers.collection.ResourceClosingIterator;
import org.neo4j.internal.kernel.api.IndexQueryConstraints;
import org.neo4j.internal.kernel.api.IndexResultScore;
import org.neo4j.internal.kernel.api.KernelReadTracer;
import org.neo4j.internal.kernel.api.PropertyIndexQuery;
import org.neo4j.internal.kernel.api.ValueIndexCursor;
import org.neo4j.internal.kernel.api.security.AccessMode;
import org.neo4j.internal.schema.IndexDescriptor;
import org.neo4j.internal.schema.IndexOrder;
import org.neo4j.internal.schema.IndexQuery.IndexQueryType;
import org.neo4j.internal.schema.IndexType;
import org.neo4j.io.IOUtils;
import org.neo4j.kernel.api.index.IndexProgressor;
import org.neo4j.kernel.api.txstate.TransactionState;
import org.neo4j.kernel.impl.newapi.TxStateIndexChanges.AddedAndRemoved;
import org.neo4j.kernel.impl.newapi.TxStateIndexChanges.AddedWithValuesAndRemoved;
import org.neo4j.memory.MemoryTracker;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.ValueGroup;
import org.neo4j.values.storable.ValueTuple;
import org.neo4j.values.storable.Values;

import static java.util.Arrays.stream;
import static org.neo4j.internal.kernel.api.Read.NO_ID;
import static org.neo4j.kernel.impl.newapi.TxStateIndexChanges.indexUpdatesForRangeSeek;
import static org.neo4j.kernel.impl.newapi.TxStateIndexChanges.indexUpdatesForRangeSeekByPrefix;
import static org.neo4j.kernel.impl.newapi.TxStateIndexChanges.indexUpdatesForScan;
import static org.neo4j.kernel.impl.newapi.TxStateIndexChanges.indexUpdatesForSeek;
import static org.neo4j.kernel.impl.newapi.TxStateIndexChanges.indexUpdatesForSuffixOrContains;
import static org.neo4j.kernel.impl.newapi.TxStateIndexChanges.indexUpdatesWithValuesForRangeSeek;
import static org.neo4j.kernel.impl.newapi.TxStateIndexChanges.indexUpdatesWithValuesForRangeSeekByPrefix;
import static org.neo4j.kernel.impl.newapi.TxStateIndexChanges.indexUpdatesWithValuesForScan;
import static org.neo4j.kernel.impl.newapi.TxStateIndexChanges.indexUpdatesWithValuesForSeek;
import static org.neo4j.kernel.impl.newapi.TxStateIndexChanges.indexUpdatesWithValuesForSuffixOrContains;

abstract class DefaultEntityValueIndexCursor extends IndexCursor implements
        ValueIndexCursor, IndexResultScore, EntityIndexSeekClient, SortedMergeJoin.Sink
{
    private static final Comparator> ASCENDING_COMPARATOR = computeComparator( Values.COMPARATOR );
    private static final Comparator> DESCENDING_COMPARATOR = computeComparator( ( o1, o2 ) -> - Values.COMPARATOR.compare( o1, o2 ) );

    private Read read;
    private long entity;
    private float score;
    private PropertyIndexQuery[] query;
    private Value[] values;
    // TODO: The following three fields are related to b-tree index type only
    // and should be removed together with all related code when b-tree is gone
    private IndexType indexType;
    private LongObjectPair cachedValues;
    private ResourceIterator> eagerPointIterator;

    private LongIterator added = ImmutableEmptyLongIterator.INSTANCE;
    private Iterator addedWithValues = Collections.emptyIterator();
    private ImmutableLongSet removed = LongSets.immutable.empty();
    private boolean needsValues;
    private IndexOrder indexOrder;
    private final MemoryTracker memoryTracker;
    private final SortedMergeJoin sortedMergeJoin = new SortedMergeJoin();
    private AccessMode accessMode;
    private boolean shortcutSecurity;

    DefaultEntityValueIndexCursor( CursorPool pool, MemoryTracker memoryTracker )
    {
        super( pool );
        this.memoryTracker = memoryTracker;
        entity = NO_ID;
        score = Float.NaN;
        indexOrder = IndexOrder.NONE;
    }

    @Override
    public final void initialize( IndexDescriptor descriptor, IndexProgressor progressor, AccessMode accessMode,
                                  boolean indexIncludesTransactionState, IndexQueryConstraints constraints, PropertyIndexQuery... query )
    {
        assert query != null;
        super.initialize( progressor );
        this.indexOrder = constraints.order();
        this.needsValues = constraints.needsValues();
        this.indexType = descriptor.getIndexType();
        sortedMergeJoin.initialize( indexOrder );

        this.query = query;

        if ( tracer != null )
        {
            tracer.onIndexSeek( );
        }

        this.accessMode = accessMode;
        shortcutSecurity = setupSecurity( descriptor );

        if ( !indexIncludesTransactionState && read.hasTxStateWithChanges() && query.length > 0 )
        {
            // Extract out the equality queries
            List exactQueryValues = new ArrayList<>( query.length );
            int i = 0;
            while ( i < query.length && query[i].type() == IndexQueryType.EXACT )
            {
                exactQueryValues.add( ((PropertyIndexQuery.ExactPredicate) query[i]).value() );
                i++;
            }
            Value[] exactValues = exactQueryValues.toArray( new Value[0] );

            if ( i == query.length )
            {
                // Only exact queries
                // No need to order, all values are the same
                this.indexOrder = IndexOrder.NONE;
                seekQuery( descriptor, exactValues );
            }
            else
            {
                PropertyIndexQuery nextQuery = query[i];
                switch ( nextQuery.type() )
                {
                case ALL_ENTRIES:
                case EXISTS:
                    // This also covers the rewritten suffix/contains for composite index
                    // If composite index all following will be exists as well so no need to consider those
                    setNeedsValuesIfRequiresOrder();
                    if ( exactQueryValues.isEmpty() )
                    {
                        // First query is allEntries or exists, use scan
                        scanQuery( descriptor );
                    }
                    else
                    {
                        rangeQuery( descriptor, exactValues, null );
                    }
                    break;

                case RANGE:
                    // This case covers first query to be range or exact followed by range
                    // If composite index all following will be exists as well so no need to consider those
                    setNeedsValuesIfRequiresOrder();
                    rangeQuery( descriptor, exactValues, (PropertyIndexQuery.RangePredicate) nextQuery );
                    break;

                case STRING_PREFIX:
                    // This case covers first query to be prefix or exact followed by prefix
                    // If composite index all following will be exists as well so no need to consider those
                    setNeedsValuesIfRequiresOrder();
                    prefixQuery( descriptor, exactValues, (PropertyIndexQuery.StringPrefixPredicate) nextQuery );
                    break;

                case STRING_SUFFIX:
                case STRING_CONTAINS:
                    // This case covers suffix/contains for singular indexes
                    // for composite index, the suffix/contains should already
                    // have been rewritten as exists + filter, so no need to consider it here
                    assert query.length == 1;
                    suffixOrContainsQuery( descriptor, nextQuery );
                    break;

                default:
                    throw new UnsupportedOperationException( "Query not supported: " + Arrays.toString( query ) );
                }
            }
        }
    }

    /**
     * If we require order, we can only do the merge sort if we also get values.
     * This implicitly relies on the fact that if we can get order, we can also get values.
     */
    private void setNeedsValuesIfRequiresOrder()
    {
        if ( indexOrder != IndexOrder.NONE )
        {
            this.needsValues = true;
        }
    }

    private boolean isRemoved( long reference )
    {
        return removed.contains( reference );
    }

    @Override
    public final boolean acceptEntity( long reference, float score, Value... values )
    {
        if ( isRemoved( reference ) || !allowed( reference ) )
        {
            return false;
        }
        else
        {
            this.entity = reference;
            this.score = score;
            this.values = values;
            return true;
        }
    }

    boolean allowsAll()
    {
        return false;
    }

    @Override
    public final boolean needsValues()
    {
        return needsValues;
    }

    @Override
    public final boolean next()
    {
        if ( indexOrder == IndexOrder.NONE )
        {
            return nextWithoutOrder();
        }
        else
        {
            return nextWithOrdering();
        }
    }

    private boolean nextWithoutOrder()
    {
        if ( !needsValues && added.hasNext() )
        {
            this.entity = added.next();
            this.values = null;
            if ( tracer != null )
            {
                traceOnEntity( tracer, entity );
            }
            return true;
        }
        else if ( needsValues && addedWithValues.hasNext() )
        {
            EntityWithPropertyValues entityWithPropertyValues = addedWithValues.next();
            this.entity = entityWithPropertyValues.getEntityId();
            this.values = entityWithPropertyValues.getValues();
            if ( tracer != null )
            {
                traceOnEntity( tracer, entity );
            }
            return true;
        }
        else if ( added.hasNext() || addedWithValues.hasNext() )
        {
            throw new IllegalStateException( "Index cursor cannot have transaction state with values and without values simultaneously" );
        }
        else
        {
            boolean next = innerNext();
            if ( tracer != null && next )
            {
                traceOnEntity( tracer, entity );
            }
            return next;
        }
    }

    private boolean nextWithOrdering()
    {
        if ( sortedMergeJoin.needsA() && addedWithValues.hasNext() )
        {
            EntityWithPropertyValues entityWithPropertyValues = addedWithValues.next();
            sortedMergeJoin.setA( entityWithPropertyValues.getEntityId(), entityWithPropertyValues.getValues() );
        }

        if ( sortedMergeJoin.needsB() && innerNextFromBuffer() )
        {
            sortedMergeJoin.setB( entity, values );
        }

        boolean next = sortedMergeJoin.next( this );
        if ( tracer != null && next )
        {
            traceOnEntity( tracer, entity );
        }
        return next;
    }

    private boolean innerNextFromBuffer()
    {
        if ( eagerPointIterator != null )
        {
            return streamPointsFromIterator();
        }

        boolean innerNext = innerNext();
        // b-tree index is the only index type for which geometric points need to be sorted in memory, because it both supports ordering
        // and stores geometric points in the order based on a space-filling curve, which is different ordering than the one used
        // by Cypher for this type
        if ( values != null && innerNext && indexOrder != IndexOrder.NONE && indexType == IndexType.BTREE )
        {
            return eagerizingPoints();
        }
        else
        {
            return innerNext;
        }
    }

    private boolean containsPoints()
    {
        for ( final var value : values )
        {
            final var valueGroup = value.valueGroup();
            if ( valueGroup == ValueGroup.GEOMETRY || valueGroup == ValueGroup.GEOMETRY_ARRAY )
            {
                return true;
            }
        }
        return false;
    }

    private boolean eagerizingPoints()
    {
        HeapTrackingArrayList> eagerPointBuffer = null;
        boolean shouldContinue = true;

        while ( shouldContinue && containsPoints() )
        {
            if ( eagerPointBuffer == null )
            {
                eagerPointBuffer = HeapTrackingArrayList.newArrayList( 256, memoryTracker );
            }
            eagerPointBuffer.add( PrimitiveTuples.pair( entity, Arrays.copyOf( values, values.length ) ));
            shouldContinue = innerNext();
        }
        if ( eagerPointBuffer != null )
        {
            if ( shouldContinue )
            {
                this.cachedValues = PrimitiveTuples.pair( entity, Arrays.copyOf( values, values.length ) );
            }

            eagerPointBuffer.sort( comparator() );
            eagerPointIterator = ResourceClosingIterator.newResourceIterator( eagerPointBuffer.autoClosingIterator(), asResource( eagerPointBuffer ) );
            return streamPointsFromIterator();
        }
        else
        {
            return true;
        }
    }

    private static Resource asResource( AutoCloseable resource )
    {
        return () -> IOUtils.closeAllUnchecked( resource );
    }

    private static Comparator> computeComparator( Comparator comparator )
    {
        return ( o1, o2 ) ->
        {
            Value[] v1 = o1.getTwo();
            Value[] v2 = o2.getTwo();
            for ( int i = 0; i < v1.length; i++ )
            {
                int compare = comparator.compare( v1[i], v2[i] );
                if ( compare != 0 )
                {
                    return compare;
                }
            }

            return 0;
        };
    }

    private Comparator> comparator()
    {
        switch ( indexOrder )
        {
        case ASCENDING:
            return ASCENDING_COMPARATOR;
        case DESCENDING:
            return DESCENDING_COMPARATOR;
        default:
            throw new IllegalStateException( "can't sort if no indexOrder defined" );
        }
    }

    private boolean streamPointsFromIterator()
    {
        if ( eagerPointIterator.hasNext() )
        {
            LongObjectPair nextPair = eagerPointIterator.next();
            entity = nextPair.getOne();
            values = nextPair.getTwo();
            return true;
        }
        else if ( cachedValues != null )
        {
            values = cachedValues.getTwo();
            entity = cachedValues.getOne();
            eagerPointIterator = null;
            cachedValues = null;
            return true;
        }
        else
        {
            return false;
        }
    }

    @Override
    public final void acceptSortedMergeJoin( long entityId, Value[] values )
    {
        this.entity = entityId;
        this.values = values;
    }

    @Override
    public final void setRead( Read read )
    {
        this.read = read;
    }

    @Override
    public final int numberOfProperties()
    {
        return query == null ? 0 : query.length;
    }

    @Override
    public final boolean hasValue()
    {
        return values != null;
    }

    @Override
    public final float score()
    {
        return score;
    }

    @Override
    public final Value propertyValue( int offset )
    {
        return values[offset];
    }

    @Override
    public final void closeInternal()
    {
        if ( !isClosed() )
        {
            closeProgressor();
            this.entity = NO_ID;
            this.score = Float.NaN;
            this.query = null;
            this.values = null;
            this.read = null;
            this.accessMode = null;
            this.added = ImmutableEmptyLongIterator.INSTANCE;
            this.addedWithValues = Collections.emptyIterator();
            this.removed = LongSets.immutable.empty();

            if ( eagerPointIterator  != null )
            {
                eagerPointIterator.close();
            }
        }
        super.closeInternal();
    }

    @Override
    public final boolean isClosed()
    {
        return isProgressorClosed();
    }

    @Override
    public String toString()
    {
        if ( isClosed() )
        {
            return implementationName() + "[closed state]";
        }
        else
        {
            String keys = query == null ? "unknown" : Arrays.toString( stream( query ).map( PropertyIndexQuery::propertyKeyId ).toArray( Integer[]::new ) );
            return implementationName() + "[entity=" + entity + ", open state with: keys=" + keys +
                    ", values=" + Arrays.toString( values ) + "]";
        }
    }

    private void prefixQuery( IndexDescriptor descriptor, Value[] equalityPrefix, PropertyIndexQuery.StringPrefixPredicate predicate )
    {
        TransactionState txState = read.txState();

        if ( needsValues )
        {
            AddedWithValuesAndRemoved changes =
                    indexUpdatesWithValuesForRangeSeekByPrefix( txState, descriptor, equalityPrefix, predicate.prefix(), indexOrder );
            addedWithValues = changes.getAdded().iterator();
            removed = removed( txState, changes.getRemoved() );
        }
        else
        {
            AddedAndRemoved changes =
                    indexUpdatesForRangeSeekByPrefix( txState, descriptor, equalityPrefix, predicate.prefix(), indexOrder );
            added = changes.getAdded().longIterator();
            removed = removed( txState, changes.getRemoved() );
        }
    }

    private void rangeQuery( IndexDescriptor descriptor, Value[] equalityPrefix, PropertyIndexQuery.RangePredicate predicate )
    {
        TransactionState txState = read.txState();

        if ( needsValues )
        {
            AddedWithValuesAndRemoved
                    changes = indexUpdatesWithValuesForRangeSeek( txState, descriptor, equalityPrefix, predicate, indexOrder );
            addedWithValues = changes.getAdded().iterator();
            removed = removed( txState, changes.getRemoved() );
        }
        else
        {
            AddedAndRemoved changes = indexUpdatesForRangeSeek( txState, descriptor, equalityPrefix, predicate, indexOrder );
            added = changes.getAdded().longIterator();
            removed = removed( txState, changes.getRemoved() );
        }
    }

    private void scanQuery( IndexDescriptor descriptor )
    {
        TransactionState txState = read.txState();

        if ( needsValues )
        {
            AddedWithValuesAndRemoved changes = indexUpdatesWithValuesForScan( txState, descriptor, indexOrder );
            addedWithValues = changes.getAdded().iterator();
            removed = removed( txState, changes.getRemoved() );
        }
        else
        {
            AddedAndRemoved changes = indexUpdatesForScan( txState, descriptor, indexOrder );
            added = changes.getAdded().longIterator();
            removed = removed( txState, changes.getRemoved() );
        }
    }

    private void suffixOrContainsQuery( IndexDescriptor descriptor, PropertyIndexQuery query )
    {
        TransactionState txState = read.txState();

        if ( needsValues )
        {
            AddedWithValuesAndRemoved changes = indexUpdatesWithValuesForSuffixOrContains( txState, descriptor, query, indexOrder );
            addedWithValues = changes.getAdded().iterator();
            removed = removed( txState, changes.getRemoved() );
        }
        else
        {
            AddedAndRemoved changes = indexUpdatesForSuffixOrContains( txState, descriptor, query, indexOrder );
            added = changes.getAdded().longIterator();
            removed = removed( txState, changes.getRemoved() );
        }
    }

    private void seekQuery( IndexDescriptor descriptor, Value[] values )
    {
        TransactionState txState = read.txState();

        if ( needsValues )
        {
            AddedWithValuesAndRemoved changes = indexUpdatesWithValuesForSeek( txState, descriptor, ValueTuple.of( values ) );
            addedWithValues = changes.getAdded().iterator();
            removed = removed( txState, changes.getRemoved() );
        }
        else
        {
            AddedAndRemoved changes = indexUpdatesForSeek( txState, descriptor, ValueTuple.of( values ) );
            added = changes.getAdded().longIterator();
            removed = removed( txState, changes.getRemoved() );
        }
    }

    final long entityReference()
    {
        return entity;
    }

    final void readEntity( EntityReader entityReader )
    {
        entityReader.read( read );
    }

    private boolean setupSecurity( IndexDescriptor descriptor )
    {
        return allowsAll() || canAccessAllDescribedEntities( descriptor, accessMode );
    }

    boolean allowed( long reference )
    {
        return shortcutSecurity || allowed( reference, accessMode );
    }

    /**
     * Check that the user is allowed to access all entities and properties given by the index descriptor.
     * 

* If {@code true} is returned, it means that security check does not need to be performed for each item in the cursor. */ abstract boolean canAccessAllDescribedEntities( IndexDescriptor descriptor, AccessMode accessMode ); /** * Gets entities removed in the current transaction that are relevant for the index. */ abstract ImmutableLongSet removed( TransactionState txState, LongSet removedFromIndex ); /** * Checks if the user is allowed to see the entity and properties the cursor is currently pointing at. */ abstract boolean allowed( long reference, AccessMode accessMode ); /** * An abstraction over {@link KernelReadTracer#onNode(long)} and {@link KernelReadTracer#onRelationship(long)}. */ abstract void traceOnEntity( KernelReadTracer tracer, long entity ); /** * Name of the concrete implementation used in {@link #toString()}. */ abstract String implementationName(); @FunctionalInterface interface EntityReader { void read( Read read ); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy