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

org.neo4j.commandline.dbms.ImportCommand Maven / Gradle / Ivy

Go to download

This component provides management functionality and product surface for Neo4j instances.

There is a newer version: 5.23.0
Show newest version
/*
 * 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.commandline.dbms;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.Optional;

import org.neo4j.commandline.admin.AdminCommand;
import org.neo4j.commandline.admin.CommandFailed;
import org.neo4j.commandline.admin.IncorrectUsage;
import org.neo4j.commandline.admin.OutsideWorld;
import org.neo4j.commandline.arguments.Arguments;
import org.neo4j.commandline.arguments.MandatoryNamedArg;
import org.neo4j.commandline.arguments.OptionalBooleanArg;
import org.neo4j.commandline.arguments.OptionalNamedArg;
import org.neo4j.commandline.arguments.OptionalNamedArgWithMetadata;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.helpers.Args;
import org.neo4j.kernel.configuration.Config;

import static org.neo4j.commandline.arguments.common.Database.ARG_DATABASE;
import static org.neo4j.csv.reader.Configuration.DEFAULT;
import static org.neo4j.tooling.ImportTool.parseFileArgumentList;
import static org.neo4j.unsafe.impl.batchimport.Configuration.DEFAULT_MAX_MEMORY_PERCENT;
import static org.neo4j.unsafe.impl.batchimport.input.csv.Configuration.COMMAS;

public class ImportCommand implements AdminCommand
{
    public static final String DEFAULT_REPORT_FILE_NAME = "import.report";
    private static final String[] allowedModes = {"database", "csv"};
    private static final Arguments databaseArguments = new Arguments()
            .withArgument( new MandatoryNamedArg( "mode", "database", "Import a pre-3.0 installation." )
            {
                @Override
                public String usage()
                {
                    return String.format( "--%s=%s", name(), exampleValue() );
                }
            } )
            .withDatabase()
            .withAdditionalConfig();

    private static final Arguments csvArguments = new Arguments()
            .withArgument( new OptionalNamedArg( "mode", "csv", "csv", "Import a collection of CSV files." )
            {
                @Override
                public String usage()
                {
                    return String.format( "[--%s=%s]", name(), exampleValue() );
                }
            } )
            .withDatabase()
            .withAdditionalConfig();

    private static final Arguments allArguments = new Arguments()
            .withDatabase()
            .withAdditionalConfig()
            .withArgument( new OptionalNamedArg( "mode", allowedModes, "csv",
                    "Import a collection of CSV files or a pre-3.0 installation." ) );

    private static void includeDatabaseArguments( Arguments arguments )
    {
        arguments
            .withArgument( new OptionalNamedArg( "from", "source-directory", "",
                    "The location of the pre-3.0 database (e.g. /data/graph.db)." ) );
    }

    private static void includeCsvArguments( Arguments arguments )
    {
        arguments
            .withArgument( new OptionalNamedArg( "report-file", "filename", DEFAULT_REPORT_FILE_NAME,
                    "File in which to store the report of the csv-import." ) )
            .withArgument( new OptionalNamedArgWithMetadata( "nodes",
                    ":Label1:Label2",
                    "\"file1,file2,...\"", "",
                    "Node CSV header and data. Multiple files will be logically seen as " +
                            "one big file from the perspective of the importer. The first line " +
                            "must contain the header. Multiple data sources like these can be " +
                            "specified in one import, where each data source has its own header. " +
                    "Note that file groups must be enclosed in quotation marks." ) )
            .withArgument( new OptionalNamedArgWithMetadata( "relationships",
                    ":RELATIONSHIP_TYPE",
                    "\"file1,file2,...\"",
                    "",
                    "Relationship CSV header and data. Multiple files will be logically " +
                            "seen as one big file from the perspective of the importer. The first " +
                            "line must contain the header. Multiple data sources like these can be " +
                            "specified in one import, where each data source has its own header. " +
                    "Note that file groups must be enclosed in quotation marks." ) )
            .withArgument( new OptionalNamedArg( "id-type", new String[]{"STRING", "INTEGER", "ACTUAL"},
                    "STRING", "Each node must provide a unique id. This is used to find the correct " +
                    "nodes when creating relationships. Possible values are:\n" +
                    "  STRING: arbitrary strings for identifying nodes,\n" +
                    "  INTEGER: arbitrary integer values for identifying nodes,\n" +
                    "  ACTUAL: (advanced) actual node ids.\n" +
                    "For more information on id handling, please see the Neo4j Manual: " +
                    "https://neo4j.com/docs/operations-manual/current/tools/import/" ) )
            .withArgument( new OptionalNamedArg( "input-encoding", "character-set", "UTF-8",
                    "Character set that input data is encoded in." ) )
            .withArgument( new OptionalBooleanArg( "ignore-extra-columns", false,
                    "If un-specified columns should be ignored during the import." ) )
            .withArgument( new OptionalBooleanArg( "ignore-duplicate-nodes", false,
                    "If duplicate nodes should be ignored during the import." ) )
            .withArgument( new OptionalBooleanArg( "ignore-missing-nodes", false,
                    "If relationships referring to missing nodes should be ignored during the import." ) )
            .withArgument( new OptionalBooleanArg( "multiline-fields",
                    DEFAULT.multilineFields(),
                    "Whether or not fields from input source can span multiple lines," +
                            " i.e. contain newline characters." ) )
            .withArgument( new OptionalNamedArg( "delimiter",
                    "delimiter-character",
                    String.valueOf( COMMAS.delimiter() ),
                    "Delimiter character between values in CSV data." ) )
            .withArgument( new OptionalNamedArg( "array-delimiter",
                    "array-delimiter-character",
                    String.valueOf( COMMAS.arrayDelimiter() ),
                    "Delimiter character between array elements within a value in CSV data." ) )
            .withArgument( new OptionalNamedArg( "quote",
                    "quotation-character",
                    String.valueOf( COMMAS.quotationCharacter() ),
                    "Character to treat as quotation character for values in CSV data. "
                            + "Quotes can be escaped as per RFC 4180 by doubling them, for example \"\" would be " +
                            "interpreted as a literal \". You cannot escape using \\." ) )
            .withArgument( new OptionalNamedArg( "max-memory",
                    "max-memory-that-importer-can-use",
                    String.valueOf( DEFAULT_MAX_MEMORY_PERCENT ) + "%",
                    "Maximum memory that neo4j-admin can use for various data structures and caching " +
                            "to improve performance. " +
                            "Values can be plain numbers, like 10000000 or e.g. 20G for 20 gigabyte, or even e.g. 70%" +
                            "." ) )
            .withArgument( new OptionalNamedArg( "f",
                    "File containing all arguments to this import",
                    "",
                    "File containing all arguments, used as an alternative to supplying all arguments on the command line directly."
                            + "Each argument can be on a separate line or multiple arguments per line separated by space."
                            + "Arguments containing spaces needs to be quoted."
                            + "Supplying other arguments in addition to this file argument is not supported." ) )
            .withArgument( new OptionalNamedArg( "high-io",
                    "true/false",
                    null,
                    "Ignore environment-based heuristics, and assume that the target storage subsystem can support parallel IO with high throughput." ) );
    }

    static
    {
        includeDatabaseArguments( databaseArguments );
        includeDatabaseArguments( allArguments );

        includeCsvArguments( csvArguments );
        includeCsvArguments( allArguments );
    }

    public static Arguments databaseArguments()
    {
        return databaseArguments;
    }

    public static Arguments csvArguments()
    {
        return csvArguments;
    }

    public static Arguments allArguments()
    {
        return allArguments;
    }

    private final Path homeDir;
    private final Path configDir;
    private final OutsideWorld outsideWorld;
    private final ImporterFactory importerFactory;

    public ImportCommand( Path homeDir, Path configDir, OutsideWorld outsideWorld )
    {
        this( homeDir, configDir, outsideWorld, new ImporterFactory() );
    }

    ImportCommand( Path homeDir, Path configDir, OutsideWorld outsideWorld, ImporterFactory importerFactory )
    {
        this.homeDir = homeDir;
        this.configDir = configDir;
        this.outsideWorld = outsideWorld;
        this.importerFactory = importerFactory;
    }

    @Override
    public void execute( String[] userSupplierArguments ) throws IncorrectUsage, CommandFailed
    {
        final String[] args;
        final String mode;
        final Optional additionalConfigFile;
        final String database;

        try
        {
            args = getImportToolArgs( userSupplierArguments );
            allArguments.parse( args );
            mode = allArguments.get( "mode" );
            database = allArguments.get( ARG_DATABASE );
            additionalConfigFile = allArguments.getOptionalPath( "additional-config" );
        }
        catch ( IllegalArgumentException e )
        {
            throw new IncorrectUsage( e.getMessage() );
        }
        catch ( IOException e )
        {
            throw new UncheckedIOException( e );
        }

        try
        {
            Config config =
                    loadNeo4jConfig( homeDir, configDir, database, loadAdditionalConfig( additionalConfigFile ) );

            Importer importer = importerFactory.getImporterForMode( mode, Args.parse( args ), config, outsideWorld );
            importer.doImport();
        }
        catch ( IllegalArgumentException e )
        {
            throw new IncorrectUsage( e.getMessage() );
        }
        catch ( IOException e )
        {
            throw new UncheckedIOException( e );
        }
    }

    private static String[] getImportToolArgs( String[] userSupplierArguments ) throws IOException, IncorrectUsage
    {
        allArguments.parse( userSupplierArguments );
        Optional fileArgument = allArguments.getOptionalPath( "f" );
        return fileArgument.isPresent() ? parseFileArgumentList( fileArgument.get().toFile() ) : userSupplierArguments;
    }

    private static Config loadAdditionalConfig( Optional additionalConfigFile )
    {
        return additionalConfigFile.map( path -> Config.fromFile( path ).build() ).orElseGet( Config::defaults );
    }

    private static Config loadNeo4jConfig( Path homeDir, Path configDir, String databaseName, Config additionalConfig )
    {
        Config config = Config.fromFile( configDir.resolve( Config.DEFAULT_CONFIG_FILE_NAME ) )
                .withHome( homeDir )
                .withConnectorsDisabled()
                .withNoThrowOnFileLoadFailure()
                .build();
        config.augment( additionalConfig );
        config.augment( GraphDatabaseSettings.active_database, databaseName );
        return config;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy