javaneo4jneo4j-java-api

Determine Neo4j database version


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?


Solution

  • 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) {}
    
        }
    
    }