Skip to content

Commit

Permalink
Adds tool for building db from transaction logs of other db
Browse files Browse the repository at this point in the history
As part of this commit also introduces a generic ConsoleInput utility
which should help building other tools as well.
  • Loading branch information
tinwelint committed Dec 1, 2015
1 parent 598b1c1 commit 1003107
Show file tree
Hide file tree
Showing 14 changed files with 2,080 additions and 15 deletions.
59 changes: 46 additions & 13 deletions community/kernel/src/main/java/org/neo4j/helpers/Args.java
Expand Up @@ -249,29 +249,57 @@ public long getDuration( String key, long defaultValueInMillis)
return value != null ? TimeUtil.parseTimeMillis.apply(value) : defaultValueInMillis;
}

public Boolean getBoolean( String key, Boolean defaultValue )
/**
* Like calling {@link #getBoolean(String, Boolean)} with {@code false} for default value.
* This is the most common case, i.e. returns {@code true} if boolean argument was specified as:
* <ul>
* <li>--myboolean</li>
* <li>--myboolean=true</li>
* </ul>
* Otherwise {@code false.
* }
* @param key argument key.
* @return {@code true} if argument was specified w/o value, or w/ value {@code true}, otherwise {@code false}.
*/
public boolean getBoolean( String key )
{
String value = getSingleOptionOrNull( key );
return getBoolean( key, false );
}

// Apparently this condition must be split like this, instead of as an elvis operator,
// because defaultValue will, in that case, be evaluated as a primitive boolean and
// a NullPointerException will insue. Odd.
if ( value != null )
{
return Boolean.parseBoolean( value );
}
return defaultValue;
/**
* Like calling {@link #getBoolean(String, Boolean, Boolean)} with {@code true} for
* {@code defaultValueIfNoValue}, i.e. specifying {@code --myarg} will interpret it as {@code true}.
*/
public Boolean getBoolean( String key, Boolean defaultValueIfNotSpecified )
{
return getBoolean( key, defaultValueIfNotSpecified, Boolean.TRUE );
}

public Boolean getBoolean( String key, Boolean defaultValueIfNotFound,
Boolean defaultValueIfNoValue )
/**
* Parses a {@code boolean} argument. There are a couple of cases:
* <ul>
* <li>The argument isn't specified. In this case the value of {@code defaultValueIfNotSpecified}
* will be returned.</li>
* <li>The argument is specified without value, for example <pre>--myboolean</pre>. In this case
* the value of {@code defaultValueIfNotSpecified} will be returned.</li>
* <li>The argument is specified with value, for example <pre>--myboolean=true</pre>.
* In this case the actual value will be returned.</li>
* </ul>
*
* @param key argument key.
* @param defaultValueIfNotSpecified used if argument not specified.
* @param defaultValueIfSpecifiedButNoValue used if argument specified w/o value.
* @return argument boolean value depending on what was specified, see above.
*/
public Boolean getBoolean( String key, Boolean defaultValueIfNotSpecified,
Boolean defaultValueIfSpecifiedButNoValue )
{
String value = getSingleOptionOrNull( key );
if ( value != null )
{
return Boolean.parseBoolean( value );
}
return this.map.containsKey( key ) ? defaultValueIfNoValue : defaultValueIfNotFound;
return this.map.containsKey( key ) ? defaultValueIfSpecifiedButNoValue : defaultValueIfNotSpecified;
}

public <T extends Enum<T>> T getEnum( Class<T> enumClass, String key, T defaultValue )
Expand All @@ -297,6 +325,11 @@ public List<String> orphans()
return new ArrayList<>( this.orphans );
}

public String[] orphansAsArray()
{
return orphans.toArray( new String[orphans.size()] );
}

public String[] asArgs()
{
List<String> list = new ArrayList<>();
Expand Down
Expand Up @@ -77,6 +77,9 @@
<license>CDDL1.1</license>
<license>GPL2/CPE</license>
</artifact>
<artifact id="com.google.code.findbugs:annotations:jar:2.0.3">
<license>LGPL2.1</license>
</artifact>
</override-licenses>
<coalesced-licenses>
<license name="Academic Free License version 3.0">
Expand All @@ -95,6 +98,7 @@
<aka>Apache Software Licenses</aka>
<aka>Apache 2</aka>
<aka>Apache License, Version 2.0</aka>
<aka>Apache License 2.0</aka>
</license>
<license name="BSD License">
<aka>BSD</aka>
Expand Down Expand Up @@ -147,6 +151,6 @@
<option>GNU General Public License, version 2 with the Classpath Exception</option>
</dual-license>
</dual-licenses>

</licensing-requirements>

720 changes: 720 additions & 0 deletions tools/LICENSES.txt

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions tools/NOTICE.txt
Expand Up @@ -24,3 +24,12 @@ Full license texts are found in LICENSES.txt.
Third-party licenses
--------------------

Apache Software License, Version 2.0
airline
Guava: Google Core Libraries for Java
javax.inject
Lucene Core

GNU Lesser General Public License, Version 2.1
FindBugs-Annotations

15 changes: 15 additions & 0 deletions tools/pom.xml
Expand Up @@ -43,6 +43,7 @@
</licenses>

<dependencies>
<!-- Community dependencies -->
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-kernel</artifactId>
Expand All @@ -60,6 +61,20 @@
<version>${project.version}</version>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-consistency-check</artifactId>
<version>${project.version}</version>
</dependency>

<!-- External dependencies -->
<dependency>
<groupId>io.airlift</groupId>
<artifactId>airline</artifactId>
<version>0.7</version>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
@@ -0,0 +1,182 @@
/*
* Copyright (c) 2002-2015 "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 Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.tools.applytx;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;

import org.neo4j.graphdb.DependencyResolver;
import org.neo4j.helpers.Args;
import org.neo4j.helpers.ArrayUtil;
import org.neo4j.helpers.Provider;
import org.neo4j.helpers.progress.ProgressListener;
import org.neo4j.io.fs.DefaultFileSystemAbstraction;
import org.neo4j.io.pagecache.PageCache;
import org.neo4j.kernel.GraphDatabaseAPI;
import org.neo4j.kernel.KernelHealth;
import org.neo4j.kernel.api.exceptions.TransactionFailureException;
import org.neo4j.kernel.api.labelscan.LabelScanStore;
import org.neo4j.kernel.impl.api.BatchingTransactionRepresentationStoreApplier;
import org.neo4j.kernel.impl.api.LegacyIndexApplierLookup;
import org.neo4j.kernel.impl.api.TransactionRepresentationCommitProcess;
import org.neo4j.kernel.impl.api.index.IndexUpdatesValidator;
import org.neo4j.kernel.impl.api.index.IndexingService;
import org.neo4j.kernel.impl.core.CacheAccessBackDoor;
import org.neo4j.kernel.impl.index.IndexConfigStore;
import org.neo4j.kernel.impl.locking.LockGroup;
import org.neo4j.kernel.impl.locking.LockService;
import org.neo4j.kernel.impl.pagecache.StandalonePageCacheFactory;
import org.neo4j.kernel.impl.transaction.CommittedTransactionRepresentation;
import org.neo4j.kernel.impl.transaction.log.IOCursor;
import org.neo4j.kernel.impl.transaction.log.LogicalTransactionStore;
import org.neo4j.kernel.impl.transaction.log.ReadOnlyTransactionStore;
import org.neo4j.kernel.impl.transaction.log.TransactionIdStore;
import org.neo4j.kernel.impl.transaction.state.NeoStoreProvider;
import org.neo4j.kernel.impl.transaction.tracing.CommitEvent;
import org.neo4j.kernel.impl.util.IdOrderingQueue;
import org.neo4j.kernel.lifecycle.LifeSupport;
import org.neo4j.kernel.monitoring.Monitors;
import org.neo4j.tools.console.input.ArgsCommand;

import static java.lang.String.format;

import static org.neo4j.helpers.progress.ProgressMonitorFactory.textual;
import static org.neo4j.kernel.impl.api.TransactionApplicationMode.RECOVERY;

public class ApplyTransactionsCommand extends ArgsCommand
{
private final File from;
private final Provider<GraphDatabaseAPI> to;

public ApplyTransactionsCommand( File from, Provider<GraphDatabaseAPI> to )
{
this.from = from;
this.to = to;
}

@Override
protected void run( Args args, PrintStream out ) throws Exception
{
TransactionIdStore txIdStore = to.instance().getDependencyResolver().resolveDependency(
TransactionIdStore.class );
long fromTx = txIdStore.getLastCommittedTransaction().transactionId();
long toTx;
if ( args.orphans().isEmpty() )
{
throw new IllegalArgumentException( "No tx specified" );
}

String whereTo = args.orphans().get( 0 );
if ( whereTo.equals( "next" ) )
{
toTx = fromTx + 1;
}
else if ( whereTo.equals( "last" ) )
{
toTx = Long.MAX_VALUE;
}
else
{
toTx = Long.parseLong( whereTo );
}

long lastApplied = applyTransactions( from, to.instance(), fromTx, toTx, out );
out.println( "Applied transactions up to and including " + lastApplied );
}

private long applyTransactions( File fromPath, GraphDatabaseAPI toDb, long fromTxExclusive, long toTxInclusive,
PrintStream out )
throws IOException, TransactionFailureException
{
DependencyResolver resolver = toDb.getDependencyResolver();
BatchingTransactionRepresentationStoreApplier applier = new BatchingTransactionRepresentationStoreApplier(
resolver.resolveDependency( IndexingService.class ),
resolver.resolveDependency( LabelScanStore.class ),
resolver.resolveDependency( NeoStoreProvider.class ).evaluate(),
resolver.resolveDependency( CacheAccessBackDoor.class ),
resolver.resolveDependency( LockService.class ),
resolver.resolveDependency( LegacyIndexApplierLookup.class ),
resolver.resolveDependency( IndexConfigStore.class ),
resolver.resolveDependency( KernelHealth.class ),
resolver.resolveDependency( IdOrderingQueue.class ) );
TransactionRepresentationCommitProcess commitProcess =
new TransactionRepresentationCommitProcess(
resolver.resolveDependency( LogicalTransactionStore.class ),
resolver.resolveDependency( NeoStoreProvider.class ).evaluate(),
applier,
resolver.resolveDependency( IndexUpdatesValidator.class ) );
LifeSupport life = new LifeSupport();
DefaultFileSystemAbstraction fileSystem = new DefaultFileSystemAbstraction();
try ( PageCache pageCache = StandalonePageCacheFactory.createPageCache( fileSystem ) )
{
LogicalTransactionStore source = life.add( new ReadOnlyTransactionStore( fileSystem, fromPath,
new Monitors(), resolver.resolveDependency( KernelHealth.class ) ) );
life.start();
long lastAppliedTx = fromTxExclusive;
// Some progress if there are more than a couple of transactions to apply
ProgressListener progress = toTxInclusive - fromTxExclusive >= 100 ?
textual( out ).singlePart( "Application progress",
toTxInclusive - fromTxExclusive ) :
ProgressListener.NONE;
try ( IOCursor<CommittedTransactionRepresentation> cursor = source.getTransactions( fromTxExclusive + 1 ) )
{
while ( cursor.next() )
{
CommittedTransactionRepresentation transaction = cursor.get();
try ( LockGroup locks = new LockGroup() )
{
commitProcess.commit( transaction.getTransactionRepresentation(), locks,
CommitEvent.NULL, RECOVERY );
progress.add( 1 );
}
catch ( final Throwable e )
{
System.err.println( "ERROR applying transaction " + transaction.getCommitEntry().getTxId() );
throw e;
}
lastAppliedTx = transaction.getCommitEntry().getTxId();
if ( lastAppliedTx == toTxInclusive )
{
break;
}
}
}
applier.closeBatch();
return lastAppliedTx;
}
finally
{
life.shutdown();
}
}

@Override
public String toString()
{
return ArrayUtil.join( new String[] {
"Applies transaction from the source onto the new db. Example:",
" apply last : applies transactions from the currently last applied and up to the last",
" transaction of source db",
" apply next : applies the next transaction onto the new db",
" apply 234 : applies up to and including tx 234 from the source db onto the new db" },
format( "%n" ) );
}
}

0 comments on commit 1003107

Please sign in to comment.