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

org.neo4j.shell.kernel.apps.Schema Maven / Gradle / Ivy

There is a newer version: 3.3.2
Show newest version
/*
 * Copyright (c) 2002-2016 "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 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.shell.kernel.apps;

import java.rmi.RemoteException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.schema.ConstraintDefinition;
import org.neo4j.graphdb.schema.ConstraintType;
import org.neo4j.graphdb.schema.IndexDefinition;
import org.neo4j.graphdb.schema.Schema.IndexState;
import org.neo4j.kernel.api.Statement;
import org.neo4j.kernel.api.index.IndexDescriptor;
import org.neo4j.kernel.impl.api.index.IndexingService;
import org.neo4j.kernel.impl.api.index.sampling.IndexSamplingMode;
import org.neo4j.shell.AppCommandParser;
import org.neo4j.shell.ColumnPrinter;
import org.neo4j.shell.Continuation;
import org.neo4j.shell.OptionDefinition;
import org.neo4j.shell.OptionValueType;
import org.neo4j.shell.Output;
import org.neo4j.shell.Session;
import org.neo4j.shell.ShellException;

import static org.neo4j.helpers.collection.Iterables.asList;
import static org.neo4j.helpers.collection.Iterables.concat;
import static org.neo4j.helpers.collection.Iterables.filter;
import static org.neo4j.helpers.collection.Iterables.indexOf;
import static org.neo4j.helpers.collection.Iterables.sort;
import static org.neo4j.shell.Continuation.INPUT_COMPLETE;

public class Schema extends TransactionProvidingApp
{
    private static final String INDENT = "  ";

    private static final Function LABEL_COMPARE_FUNCTION =
            index -> index.getLabel().name();

    {
        addOptionDefinition( "l", new OptionDefinition( OptionValueType.MUST,
                "Specifies which label selected operation is about" ) );
        addOptionDefinition( "r", new OptionDefinition( OptionValueType.MUST,
                "Specifies which relationship type selected operation is about" ) );
        addOptionDefinition( "p", new OptionDefinition( OptionValueType.MUST,
                "Specifies which property selected operation is about" ) );
        addOptionDefinition( "a", new OptionDefinition( OptionValueType.NONE,
                "Used together with schema sample to indicate that all indexes should be sampled" ) );
        addOptionDefinition( "f", new OptionDefinition( OptionValueType.NONE,
                "Used together with schema sample to force indexes to be sampled" ) );
        addOptionDefinition( "v", new OptionDefinition( OptionValueType.NONE,
                "Verbose output of failure descriptions etc." ) );
    }

    @Override
    public String getDescription()
    {
        return "Accesses db schema. Usage: schema  \n" +
               "Listing indexes\n" +
               "  schema ls\n" +
               "  schema ls -l :Person\n" +
               "  schema ls -r :KNOWS\n" +
               "Sample all indexes\n" +
               "  schema sample -a\n" +
               "Sample a specific index\n" +
               "  schema sample -l :Person -p name\n" +
               "  schema sample -r :KNOWS -p since\n" +
               "Force a sampling of a specific index\n" +
               "  schema sample -f -l :Person -p name\n" +
               "Awaiting indexes to come online\n" +
               "  schema await -l :Person -p name\n" +
               "Print indexing progress\n" +
               "  schema progress\n" +
               "  schema progress -l :Person\n" +
               "  schema progress -l :Person -p name";
    }

    @Override
    protected Continuation exec( AppCommandParser parser, Session session, Output out ) throws Exception
    {
        String action = parser.argumentWithDefault( 0, "ls" );
        org.neo4j.graphdb.schema.Schema schema = getServer().getDb().schema();
        Label[] labels = parseLabels( parser );
        RelationshipType[] relTypes = parseRelTypes( parser );
        String property = parser.option( "p", null );
        boolean sampleAll = parser.options().containsKey( "a" );
        boolean forceSample = parser.options().containsKey( "f" );
        boolean verbose = parser.options().containsKey( "v" );

        switch ( action )
        {
        case "await":
            if ( relTypes.length > 0 )
            {
                throw new ShellException( "It is only possible to await nodes related index" );
            }
            awaitIndexes( out, schema, labels, property );
            break;
        case "progress":
            if ( relTypes.length > 0 )
            {
                throw new ShellException( "It is only possible to show progress on nodes related index" );
            }
            printIndexProgress( out, schema, labels, property );
            break;
        case "ls":
            listIndexesAndConstraints( out, schema, labels, relTypes, property, verbose );
            break;
        case "sample":
            if ( relTypes.length > 0 )
            {
                throw new ShellException( "It is only possible to sample nodes related index" );
            }
            sampleIndexes( labels, property, sampleAll, forceSample );
            break;
        default:
            out.println( "Unknown action: " + action + "\nUSAGE:\n" + getDescription() );
            break;
        }

        return INPUT_COMPLETE;
    }

    private void listIndexesAndConstraints( Output out, org.neo4j.graphdb.schema.Schema schema, Label[] labels,
            RelationshipType[] relTypes, String property, boolean verbose ) throws RemoteException
    {
        if ( labels.length > 0 && relTypes.length == 0 )
        {
            listNodeIndexesAndConstraints( out, schema, labels, property, verbose );
        }
        else if ( relTypes.length > 0 && labels.length == 0 )
        {
            listRelationshipIndexesAndConstraints( out, schema, relTypes, property, verbose );
        }
        else
        {
            listAllIndexesAndConstraints( out, schema, labels, relTypes, property, verbose );
        }
    }

    private void sampleIndexes( Label[] labels, String property, boolean sampleAll, boolean forceSample )
            throws ShellException
    {

        IndexingService indexingService = getServer().getDb().getDependencyResolver().resolveDependency(
                IndexingService.class );
        if ( indexingService == null )
        {
            throw new ShellException( "Internal error: failed to resolve IndexingService" );
        }

        IndexSamplingMode samplingMode = getSamplingMode( forceSample );

        // Trigger sampling for all indices
        if ( sampleAll )
        {
            indexingService.triggerIndexSampling( samplingMode );
            return;
        }

        validateLabelsAndProperty( labels, property );

        Statement statement = getServer().getStatement();

        int labelKey = statement.readOperations().labelGetForName( labels[0].name() );
        int propertyKey = statement.readOperations().propertyKeyGetForName( property );

        if ( labelKey == -1 )
        {
            throw new ShellException( "No label associated with '" + labels[0].name() + "' was found" );
        }
        if ( propertyKey == -1 )
        {
            throw new ShellException( "No property associated with '" + property + "' was found" );
        }

        indexingService.triggerIndexSampling( new IndexDescriptor( labelKey, propertyKey ), samplingMode );
    }

    private IndexSamplingMode getSamplingMode( boolean forceSample )
    {
        if ( forceSample )
        {
            return IndexSamplingMode.TRIGGER_REBUILD_ALL;
        }
        else
        {
            return IndexSamplingMode.TRIGGER_REBUILD_UPDATED;
        }
    }

    private void validateLabelsAndProperty( Label[] labels, String property ) throws ShellException
    {
        if ( labels.length == 0 && property == null )
        {
            throw new ShellException( "Invalid usage of sample. \nUSAGE:\n" + getDescription() );
        }

        if ( labels.length > 1 )
        {
            throw new ShellException( "Only one label must be provided" );
        }

        // If we provide one we must also provide the other
        if ( property == null || labels.length == 0 )
        {
            throw new ShellException( "Provide both the property and the label, or run with -a to sample all indexes" );
        }
    }

    private void printIndexProgress( Output out, org.neo4j.graphdb.schema.Schema schema, Label[] labels,
            String property ) throws RemoteException
    {
        for ( IndexDefinition index : indexesByLabelAndProperty( schema, labels, property ) )
        {
            out.println( String.format( "%s: %1.1f%%", index.getLabel().name(),
                    schema.getIndexPopulationProgress( index ).getCompletedPercentage() ) );
        }
    }

    private void awaitIndexes( Output out, org.neo4j.graphdb.schema.Schema schema, Label[] labels, String property )
            throws RemoteException
    {
        for ( IndexDefinition index : indexesByLabelAndProperty( schema, labels, property ) )
        {
            if ( schema.getIndexState( index ) != IndexState.ONLINE )
            {
                out.println( String.format( "Awaiting :%s ON %s %s", index.getLabel().name(),
                        asList( index.getPropertyKeys() ), IndexState.ONLINE ) );
                schema.awaitIndexOnline( index, 10000, TimeUnit.DAYS );
            }
        }
    }

    private void listNodeIndexesAndConstraints( Output out, org.neo4j.graphdb.schema.Schema schema, Label[] labels,
            String property, boolean verbose ) throws RemoteException
    {
        reportNodeIndexes( out, schema, labels, property, verbose );
        reportNodeConstraints( out, schema, labels, property );
    }

    private void listRelationshipIndexesAndConstraints( Output out, org.neo4j.graphdb.schema.Schema schema,
            RelationshipType[] types, String property, boolean verbose ) throws RemoteException
    {
        // no relationship indexes atm
        reportRelationshipConstraints( out, schema, types, property );
    }

    private void listAllIndexesAndConstraints( Output out, org.neo4j.graphdb.schema.Schema schema, Label[] labels,
            RelationshipType[] types, String property, boolean verbose ) throws RemoteException
    {
        reportNodeIndexes( out, schema, labels, property, verbose );
        reportAllConstraints( out, schema, labels, types, property );
    }

    private void reportNodeConstraints( Output out, org.neo4j.graphdb.schema.Schema schema,
            Label[] labels, String property ) throws RemoteException
    {
        Iterable nodeConstraints = constraintsByLabelAndProperty( schema, labels, property );
        reportConstraints( out, nodeConstraints );
    }

    private void reportRelationshipConstraints( Output out, org.neo4j.graphdb.schema.Schema schema,
            RelationshipType[] types, String property ) throws RemoteException
    {
        Iterable relConstraints = constraintsByTypeAndProperty( schema, types, property );
        reportConstraints( out, relConstraints );
    }

    private void reportAllConstraints( Output out, org.neo4j.graphdb.schema.Schema schema,
            Label[] labels, RelationshipType[] types, String property ) throws RemoteException
    {
        Iterable allConstraints = concat(
                constraintsByLabelAndProperty( schema, labels, property ),
                constraintsByTypeAndProperty( schema, types, property ) );

        reportConstraints( out, allConstraints );
    }

    private void reportConstraints( Output out, Iterable constraints ) throws RemoteException
    {
        int j = 0;
        for ( ConstraintDefinition constraint : constraints )
        {
            if ( j == 0 )
            {
                out.println();
                out.println( "Constraints" );
            }

            out.println( indent( constraint.toString() ) );
            j++;

        }
        if ( j == 0 )
        {
            out.println();
            out.println( "No constraints" );
        }
    }

    private void reportNodeIndexes( Output out, org.neo4j.graphdb.schema.Schema schema, Label[] labels, String property,
            boolean verbose ) throws RemoteException
    {
        ColumnPrinter printer = new ColumnPrinter( indent( "ON " ), "", "" );
        Iterable indexes = indexesByLabelAndProperty( schema, labels, property );

        int i = 0;
        for ( IndexDefinition index : sort( indexes, LABEL_COMPARE_FUNCTION ) )
        {
            if ( i == 0 )
            {
                out.println( "Indexes" );
            }
            String labelAndProperties = String.format( ":%s(%s)", index.getLabel().name(), commaSeparate( index
                    .getPropertyKeys() ) );

            IndexState state = schema.getIndexState( index );
            String uniqueOrNot = index.isConstraintIndex() ? "(for uniqueness constraint)" : "";

            printer.add( labelAndProperties, state, uniqueOrNot );
            if ( verbose && state == IndexState.FAILED )
            {
                printer.addRaw( schema.getIndexFailure( index ) );
            }
            i++;
        }
        if ( i == 0 )
        {
            out.println( "No indexes" );
        }
        else
        {
            printer.print( out );
        }
    }

    private String commaSeparate( Iterable keys )
    {
        StringBuilder builder = new StringBuilder();
        boolean first = true;
        for ( String key : keys )
        {
            if ( !first )
            {
                builder.append( ", " );

            }
            else
            {
                first = false;
            }
            builder.append( key );
        }
        return builder.toString();
    }

    private Iterable indexesByLabelAndProperty( org.neo4j.graphdb.schema.Schema schema,
            Label[] labels, final String property )
    {
        Iterable indexes = indexesByLabel( schema, labels );
        if ( property != null )
        {
            indexes = filter( index -> indexOf( property, index.getPropertyKeys() ) != -1, indexes );
        }
        return indexes;
    }

    private Iterable constraintsByLabelAndProperty( org.neo4j.graphdb.schema.Schema schema,
            final Label[] labels, final String property )
    {
        return filter( constraint -> isNodeConstraint( constraint ) &&
                             hasLabel( constraint, labels ) &&
                             isMatchingConstraint( constraint, property ), schema.getConstraints() );
    }

    private Iterable constraintsByTypeAndProperty( org.neo4j.graphdb.schema.Schema schema,
            final RelationshipType[] types, final String property )
    {
        return filter( constraint -> isRelationshipConstraint( constraint ) &&
                             hasType( constraint, types ) &&
                             isMatchingConstraint( constraint, property ), schema.getConstraints() );
    }

    private boolean hasLabel( ConstraintDefinition constraint, Label[] labels )
    {
        if ( labels.length == 0 )
        {
            return true;
        }

        for ( Label label : labels )
        {
            if ( constraint.getLabel().name().equals( label.name() ) )
            {
                return true;
            }
        }

        return false;
    }

    private static boolean hasType( ConstraintDefinition constraint, RelationshipType[] types )
    {
        if ( types.length == 0 )
        {
            return true;
        }

        for ( RelationshipType type : types )
        {
            if ( constraint.getRelationshipType().name().equals( type.name() ) )
            {
                return true;
            }
        }

        return false;
    }

    private static boolean isNodeConstraint( ConstraintDefinition constraint )
    {
        return constraint.isConstraintType( ConstraintType.UNIQUENESS ) ||
               constraint.isConstraintType( ConstraintType.NODE_PROPERTY_EXISTENCE );
    }

    private static boolean isRelationshipConstraint( ConstraintDefinition constraint )
    {
        return constraint.isConstraintType( ConstraintType.RELATIONSHIP_PROPERTY_EXISTENCE );
    }

    private boolean isMatchingConstraint( ConstraintDefinition constraint, final String property )
    {
        if ( property == null )
        {
            return true;
        }

        return indexOf( property, constraint.getPropertyKeys() ) != -1;
    }

    private Iterable indexesByLabel( org.neo4j.graphdb.schema.Schema schema, Label[] labels )
    {
        Iterable indexes = schema.getIndexes();
        for ( final Label label : labels )
        {
            indexes = filter( item -> {
                return item.getLabel().name().equals( label.name() );
            }, indexes );
        }
        return indexes;
    }

    private static String indent( String str )
    {
        return INDENT + str;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy