The Neo4j Java API automatically updates outdated databases to the current version when I call
new GraphDatabaseFactory().newEmbeddedDatabase(File storeDir)
I would like to check what version the database is before doing that. Is there a way to do that with the Java API? Or alternatively: Where is the database version stored so I can read it out manually?
Release version
I dug into the Neo4j API source and found an answer. Neo4j reads out the previous version from the debug.log
file in the logs
directory. Whenever the database is started the version is printed into the log file as Kernel version: (this is where you'll find the version)
. For instance it could look like this:
2017-11-21 06:21:43.460+0000 INFO [o.n.k.i.DiagnosticsManager] Kernel version: 3.3.0,5b700972242a5ec3e0140261120f2845fb3520ad
You could read out the debug.log the Neo4j way:
import java.io.File;
import org.neo4j.io.fs.DefaultFileSystemAbstraction;
import org.neo4j.io.fs.FileSystemAbstraction;
import org.neo4j.kernel.impl.transaction.log.LogTailScanner;
import org.neo4j.kernel.impl.transaction.log.PhysicalLogFile;
import org.neo4j.kernel.impl.transaction.log.PhysicalLogFiles;
import org.neo4j.kernel.impl.transaction.log.ReadableClosablePositionAwareChannel;
import org.neo4j.kernel.impl.transaction.log.entry.LogEntryReader;
import org.neo4j.kernel.impl.transaction.log.entry.LogEntryVersion;
import org.neo4j.kernel.impl.transaction.log.entry.VersionAwareLogEntryReader;
public class Neo4jVersionChecker {
//Note that this method needs the store directory NOT the debug.log file
public static String getNeo4jVersion(File storeDir) {
FileSystemAbstraction fileSystem = new DefaultFileSystemAbstraction();
final PhysicalLogFiles logFiles = new PhysicalLogFiles( storeDir, PhysicalLogFile.DEFAULT_NAME, fileSystem );
final LogEntryReader<ReadableClosablePositionAwareChannel> logEntryReader = new VersionAwareLogEntryReader<>();
LogTailScanner tailScanner = new LogTailScanner( logFiles, fileSystem, logEntryReader );
LogEntryVersion version = tailScanner.getTailInformation().latestLogEntryVersion;
if(version!=null) {
return version.toString();
} else {
return null;
}
}
}
The above method returns V3_0_10
for a debug.log
whose latest Kernel version
entry is the one above.
Unfortunately the Neo4j way is not very precise. As you can see the Kernel version
from the debug.log
starts with 3.3.0
but the Neo method says it is V3_0_10
. I'm assuming this has something to do with the way Neo handles versions internally.
But since we now know how Neo4j gets the version, we can do the same thing in a more exact way:
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.input.ReversedLinesFileReader;
public class VersionChecker {
public static String getVersion(File storeDir) {
File debugLog = new File(storeDir, "logs" + File.separator + "debug.log");
if(debugLog.exists()) {
try {
//The ReversedLinesFileReader reads the last line of a file first and so on
ReversedLinesFileReader reader = new ReversedLinesFileReader(debugLog, StandardCharsets.UTF_8);
//Read last line
String line = reader.readLine();
while(line!=null) {
//Line can't be null at this point
if(line.contains("Kernel version: ")) {
//This line contains the version
line = line.substring(line.indexOf("Kernel version: ")).substring(16); //get rid of everything except the version
line = line.split(",")[0]; //get rid of the second part of the Kernel version that we don't want
return line;
}
//Next line
line = reader.readLine();
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
The method above will return 3.3.0
.
Store version
Of course both of these methods only work if there is a debug.log file. Not all previous Neo4j versions have them. As long as the store directory contains the neostore
file you can read out the store version
which is not as nice as reading out the release version but at least it is something. So here's how that works:
There is a Neo4j class called StoreVersionCheck
which contains a very handy method called getVersion(File neostoreFile)
. Unfortunately we need an instance of something called a PageCache
to initialize an instance of StoreVersionCheck
. We can make a PageCache
, so that is what we'll do.
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Consumer;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.io.fs.DefaultFileSystemAbstraction;
import org.neo4j.io.fs.FileSystemAbstraction;
import org.neo4j.io.pagecache.PageCache;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.impl.factory.GraphDatabaseFacadeFactory;
import org.neo4j.kernel.impl.pagecache.ConfiguringPageCacheFactory;
import org.neo4j.kernel.impl.storemigration.StoreVersionCheck;
import org.neo4j.kernel.impl.util.Neo4jJobScheduler;
import org.neo4j.kernel.monitoring.Monitors;
import org.neo4j.kernel.monitoring.tracing.Tracers;
import org.neo4j.logging.Log;
import org.neo4j.logging.Logger;
import org.neo4j.scheduler.JobScheduler;
public class StoreVersionChecker {
public static String getStoreVersion(File storeDir) {
File storeFile = new File(storeDir, "neostore");
if(!storeFile.exists()) {
return null;
}
StoreVersionCheck check = new StoreVersionCheck(buildPageCache());
try {
Optional<String> version = check.getVersion(storeFile);
if(version.isPresent()) {
return version.get();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private static PageCache buildPageCache() {
FileSystemAbstraction fileSystem = new DefaultFileSystemAbstraction();
Config config = Config.defaults();
Log pageCacheLog = new DummyLog();
String desiredImplementationName = config.get( GraphDatabaseFacadeFactory.Configuration.tracer );
Monitors monitors = new Monitors();
JobScheduler jobScheduler = new Neo4jJobScheduler();
Tracers tracers = new Tracers( desiredImplementationName, new DummyLog(), monitors, jobScheduler );
ConfiguringPageCacheFactory pageCacheFactory = new ConfiguringPageCacheFactory(fileSystem, config, tracers.pageCacheTracer, tracers.pageCursorTracerSupplier, pageCacheLog );
PageCache pageCache = pageCacheFactory.getOrCreatePageCache();
if ( config.get( GraphDatabaseSettings.dump_configuration ) )
{
pageCacheFactory.dumpConfiguration();
}
return pageCache;
}
//We need this so we can give the Tracers a Log
private static class DummyLog implements Log {
@Override
public boolean isDebugEnabled() {return false;}
@Override
public Logger debugLogger() {return null;}
@Override
public void debug(String message) {}
@Override
public void debug(String message, Throwable throwable) {}
@Override
public void debug(String format, Object... arguments) {}
@Override
public Logger infoLogger() {return null;}
@Override
public void info(String message) {}
@Override
public void info(String message, Throwable throwable) {}
@Override
public void info(String format, Object... arguments) {}
@Override
public Logger warnLogger() {return null;}
@Override
public void warn(String message) {}
@Override
public void warn(String message, Throwable throwable) {}
@Override
public void warn(String format, Object... arguments) {}
@Override
public Logger errorLogger() {return null;}
@Override
public void error(String message) {}
@Override
public void error(String message, Throwable throwable) {}
@Override
public void error(String format, Object... arguments) {}
@Override
public void bulk(Consumer<Log> consumer) {}
}
}