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

org.neo4j.kernel.impl.index.schema.StringSchemaKey Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2020 "Graph Foundation,"
 * Graph Foundation, Inc. [https://graphfoundation.org]
 *
 * This file is part of ONgDB.
 *
 * ONgDB 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 .
 */
/*
 * Copyright (c) 2002-2020 "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.index.schema;

import java.util.Arrays;

import org.neo4j.index.internal.gbptree.GBPTree;
import org.neo4j.string.UTF8;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.Values;

import static java.lang.String.format;

/**
 * Includes value and entity id (to be able to handle non-unique values). A value can be any {@link String},
 * or rather any string that {@link GBPTree} can handle.
 */
class StringSchemaKey extends NativeSchemaKey
{
    static final int ENTITY_ID_SIZE = Long.BYTES;

    private boolean ignoreLength;

    // UTF-8 bytes, grows on demand. Actual length is dictated by bytesLength field.
    byte[] bytes;
    int bytesLength;
    // Set to true when the internal byte[] have been handed out to an UTF8Value, so that the next call to setBytesLength
    // will be forced to allocate a new array. The byte[] isn't cleared with null since this key still logically contains those bytes.
    private boolean bytesDereferenced;

    int size()
    {
        return ENTITY_ID_SIZE + bytesLength;
    }

    @Override
    protected Value assertCorrectType( Value value )
    {
        if ( !Values.isTextValue( value ) )
        {
            throw new IllegalArgumentException(
                    "Key layout does only support strings, tried to create key from " + value );
        }
        return value;
    }

    @Override
    void initialize( long entityId )
    {
        super.initialize( entityId );
        ignoreLength = false;
    }

    @Override
    public Value asValue()
    {
        if ( bytes == null )
        {
            return Values.NO_VALUE;
        }

        // Dereference our bytes so that we won't overwrite it on next read
        bytesDereferenced = true;
        return Values.utf8Value( bytes, 0, bytesLength );
    }

    @Override
    void initValueAsLowest()
    {
        bytes = null;
    }

    @Override
    void initValueAsHighest()
    {
        bytes = null;
    }

    void initAsPrefixLow( String prefix )
    {
        writeString( prefix );
        initialize( Long.MIN_VALUE );
        // Don't set ignoreLength = true here since the "low" a.k.a. left side of the range should care about length.
        // This will make the prefix lower than those that matches the prefix (their length is >= that of the prefix)
    }

    void initAsPrefixHigh( String prefix )
    {
        writeString( prefix );
        initialize( Long.MAX_VALUE );
        ignoreLength = true;
    }

    private boolean isHighest()
    {
        return getCompareId() && getEntityId() == Long.MAX_VALUE && bytes == null;
    }

    /**
     * Compares the value of this key to that of another key.
     * This method is expected to be called in scenarios where inconsistent reads may happen (and later retried).
     *
     * @param other the {@link StringSchemaKey} to compare to.
     * @return comparison against the {@code other} {@link StringSchemaKey}.
     */
    @Override
    int compareValueTo( StringSchemaKey other )
    {
        if ( bytes != other.bytes )
        {
            if ( bytes == null )
            {
                return isHighest() ? 1 : -1;
            }
            if ( other.bytes == null )
            {
                return other.isHighest() ? -1 : 1;
            }
        }
        else
        {
            return 0;
        }

        return unsignedByteArrayCompare( bytes, bytesLength, other.bytes, other.bytesLength, ignoreLength | other.ignoreLength );
    }

    private static int unsignedByteArrayCompare( byte[] a, int aLength, byte[] b, int bLength, boolean ignoreLength )
    {
        assert a != null && b != null : "Null arrays not supported.";

        if ( a == b && aLength == bLength )
        {
            return 0;
        }

        int length = Math.min( aLength, bLength );
        for ( int i = 0; i < length; i++ )
        {
            int compare = Short.compare( (short) (a[i] & 0xFF), (short) (b[i] & 0xFF) );
            if ( compare != 0 )
            {
                return compare;
            }
        }

        return ignoreLength ? 0 : Integer.compare( aLength, bLength );
    }

    @Override
    public String toString()
    {
        return format( "value=%s,entityId=%d,bytes=%s",
                asValue(),
                getEntityId(),
                bytes == null ? "null" : Arrays.toString( Arrays.copyOf( bytes, bytesLength ) ) );
    }

    @Override
    public void writeString( String value )
    {
        bytes = UTF8.encode( value );
        bytesLength = bytes.length;
        bytesDereferenced = false;
    }

    @Override
    public void writeString( char value )
    {
        writeString( String.valueOf( value ) );
    }

    void copyFrom( StringSchemaKey key )
    {
        setBytesLength( key.bytesLength );
        System.arraycopy( key.bytes, 0, bytes, 0, key.bytesLength );
        setEntityId( key.getEntityId() );
        setCompareId( key.getCompareId() );
    }

    /**
     * Ensures that the internal byte[] is long enough, or longer than the given {@code length}.
     * Also sets the internal {@code bytesLength} field to the given {@code length} so that interactions with the byte[]
     * from this point on will use that for length, instead of the length of the byte[].
     *
     * @param length minimum length that the internal byte[] needs to be.
     */
    void setBytesLength( int length )
    {
        if ( bytesDereferenced || bytes == null || bytes.length < length )
        {
            bytesDereferenced = false;

            // allocate a bit more than required so that there's a higher chance that this byte[] instance
            // can be used for more keys than just this one
            bytes = new byte[length + length / 2];
        }
        bytesLength = length;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy