diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java b/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java index e0ebb45bb9..209860f34e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java @@ -22,8 +22,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.v1.Logger; import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.Logger; import org.neo4j.driver.v1.Record; import org.neo4j.driver.v1.Session; import org.neo4j.driver.v1.Statement; @@ -144,6 +144,12 @@ public void close() } } + @Override + public String server() + { + return connection.server(); + } + @Override public Transaction beginTransaction() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java b/driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java index 90f14e8e50..1303e9ce80 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java @@ -82,12 +82,19 @@ public void done() keys = new ArrayList<>(); } } + + @Override + public void resultAvailableAfter( long l ) + { + pullAllResponseCollector.resultAvailableAfter( l ); + } }; } private StreamCollector newPullAllResponseCollector( Statement statement ) { final SummaryBuilder summaryBuilder = new SummaryBuilder( statement ); + return new StreamCollector.NoOperationStreamCollector() { @Override @@ -131,6 +138,18 @@ public void done() { summary = summaryBuilder.build(); done = true; } + + @Override + public void resultAvailableAfter(long l) + { + summaryBuilder.resultAvailableAfter( l ); + } + + @Override + public void resultConsumedAfter(long l) + { + summaryBuilder.resultConsumedAfter( l ); + } }; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnection.java b/driver/src/main/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnection.java index 89ce975d1a..5d197c2441 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnection.java @@ -228,4 +228,10 @@ private void markAsInUse() "do that is to give each thread its own dedicated session." ); } } + + @Override + public String server() + { + return delegate.server(); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnection.java b/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnection.java index a04502600c..673cfe8e5c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnection.java @@ -47,6 +47,7 @@ public class SocketConnection implements Connection private final Queue pendingMessages = new LinkedList<>(); private final SocketResponseHandler responseHandler; private AtomicBoolean interrupted = new AtomicBoolean( false ); + private final StreamCollector.InitStreamCollector initStreamCollector = new StreamCollector.InitStreamCollector(); private final SocketClient socket; @@ -70,7 +71,7 @@ public SocketConnection( BoltServerAddress address, SecurityPlan securityPlan, L @Override public void init( String clientName, Map authToken ) { - queueMessage( new InitMessage( clientName, authToken ), StreamCollector.INIT ); + queueMessage( new InitMessage( clientName, authToken ), initStreamCollector ); sync(); } @@ -231,4 +232,10 @@ public boolean isInterrupted() { return interrupted.get(); } + + @Override + public String server() + { + return initStreamCollector.server( ); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/SocketResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/net/SocketResponseHandler.java index ea62973f20..cff56c80b3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/net/SocketResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/net/SocketResponseHandler.java @@ -84,15 +84,42 @@ public void handleFailureMessage( String code, String message ) public void handleSuccessMessage( Map meta ) { StreamCollector collector = collectors.remove(); + collectServer( collector, meta.get( "server" )); collectFields( collector, meta.get( "fields" ) ); collectType( collector, meta.get( "type" ) ); collectStatistics( collector, meta.get( "stats" ) ); collectPlan( collector, meta.get( "plan" ) ); collectProfile( collector, meta.get( "profile" ) ); collectNotifications( collector, meta.get( "notifications" ) ); + collectResultAvailableAfter( collector, meta.get("result_available_after")); + collectResultConsumedAfter( collector, meta.get("result_consumed_after")); collector.doneSuccess(); } + private void collectServer( StreamCollector collector, Value server ) + { + if (server != null) + { + collector.server( server.asString() ); + } + } + + private void collectResultAvailableAfter( StreamCollector collector, Value resultAvailableAfter ) + { + if (resultAvailableAfter != null) + { + collector.resultAvailableAfter(resultAvailableAfter.asLong()); + } + } + + private void collectResultConsumedAfter( StreamCollector collector, Value resultConsumedAfter ) + { + if (resultConsumedAfter != null) + { + collector.resultConsumedAfter(resultConsumedAfter.asLong()); + } + } + private void collectNotifications( StreamCollector collector, Value notifications ) { if ( notifications != null ) diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/pooling/PooledConnection.java b/driver/src/main/java/org/neo4j/driver/internal/net/pooling/PooledConnection.java index f0b63859fc..c4c9406761 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/net/pooling/PooledConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/net/pooling/PooledConnection.java @@ -230,6 +230,12 @@ public boolean isInterrupted() return delegate.isInterrupted(); } + @Override + public String server() + { + return delegate.server(); + } + public void dispose() { delegate.close(); @@ -280,7 +286,6 @@ private boolean isClientOrTransientError( RuntimeException e ) public long idleTime() { - long idleTime = clock.millis() - lastUsed; - return idleTime; + return clock.millis() - lastUsed; } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/spi/Connection.java b/driver/src/main/java/org/neo4j/driver/internal/spi/Connection.java index ecdbac18e0..3f5807476c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/spi/Connection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/spi/Connection.java @@ -100,7 +100,7 @@ public interface Connection extends AutoCloseable * runnable. This is used in the driver to clean up resources associated with * the connection, like an open transaction. * - * @param runnable + * @param runnable To be run on error. */ void onError( Runnable runnable ); @@ -117,4 +117,10 @@ public interface Connection extends AutoCloseable * @return true if the current session statement execution has been interrupted by another thread, otherwise false */ boolean isInterrupted(); + + /** + * Returns the version of the server connected to. + * @return The version of the server connected to. + */ + String server(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/spi/StreamCollector.java b/driver/src/main/java/org/neo4j/driver/internal/spi/StreamCollector.java index 09a459b492..a00d755595 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/spi/StreamCollector.java +++ b/driver/src/main/java/org/neo4j/driver/internal/spi/StreamCollector.java @@ -50,15 +50,27 @@ public void doneIgnored() } }; - StreamCollector INIT = new NoOperationStreamCollector() + class InitStreamCollector extends NoOperationStreamCollector { + private String server; @Override public void doneIgnored() { throw new ClientException( "Invalid server response message `IGNORED` received for client message `INIT`." ); } - }; + + @Override + public void server( String server ) + { + this.server = server; + } + + public String server() + { + return server; + } + } StreamCollector RESET = new ResetStreamCollector(); @@ -144,6 +156,15 @@ public void doneIgnored() { done(); } + + @Override + public void resultAvailableAfter( long l ) {} + + @Override + public void resultConsumedAfter( long l ) {} + + @Override + public void server( String server ){} } // TODO: This should be modified to simply have head/record/tail methods @@ -169,5 +190,11 @@ public void doneIgnored() void doneFailure( Neo4jException error ); void doneIgnored(); + + void resultAvailableAfter( long l ); + + void resultConsumedAfter( long l ); + + void server( String server ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/summary/SummaryBuilder.java b/driver/src/main/java/org/neo4j/driver/internal/summary/SummaryBuilder.java index f69f0d7688..dd5bbc942c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/summary/SummaryBuilder.java +++ b/driver/src/main/java/org/neo4j/driver/internal/summary/SummaryBuilder.java @@ -20,18 +20,19 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import org.neo4j.driver.internal.spi.StreamCollector; +import org.neo4j.driver.v1.Statement; +import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.exceptions.Neo4jException; import org.neo4j.driver.v1.summary.Notification; import org.neo4j.driver.v1.summary.Plan; import org.neo4j.driver.v1.summary.ProfiledPlan; import org.neo4j.driver.v1.summary.ResultSummary; -import org.neo4j.driver.v1.Statement; import org.neo4j.driver.v1.summary.StatementType; import org.neo4j.driver.v1.summary.SummaryCounters; -import org.neo4j.driver.v1.Value; -import org.neo4j.driver.v1.exceptions.ClientException; public class SummaryBuilder implements StreamCollector { @@ -42,6 +43,8 @@ public class SummaryBuilder implements StreamCollector private Plan plan = null; private ProfiledPlan profile; private List notifications = null; + private long resultAvailableAfter = -1L; + private long resultConsumedAfter = -1L; public SummaryBuilder( Statement statement ) { @@ -148,6 +151,24 @@ public void doneIgnored() // intentionally empty } + @Override + public void resultAvailableAfter( long l ) + { + this.resultAvailableAfter = l; + } + + @Override + public void resultConsumedAfter( long l ) + { + this.resultConsumedAfter = l; + } + + @Override + public void server( String server ) + { + // intentionally empty + } + public ResultSummary build() { return new ResultSummary() @@ -199,6 +220,18 @@ public List notifications() { return notifications == null ? new ArrayList() : notifications; } + + @Override + public long resultAvailableAfter( TimeUnit timeUnit ) + { + return timeUnit.convert( resultAvailableAfter, TimeUnit.MILLISECONDS ); + } + + @Override + public long resultConsumedAfter( TimeUnit timeUnit ) + { + return timeUnit.convert( resultConsumedAfter, TimeUnit.MILLISECONDS ); + } }; } } diff --git a/driver/src/main/java/org/neo4j/driver/v1/Session.java b/driver/src/main/java/org/neo4j/driver/v1/Session.java index 80a44dd44b..4444bec2b5 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Session.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Session.java @@ -75,4 +75,10 @@ public interface Session extends Resource, StatementRunner */ @Override void close(); + + /** + * Returns a string telling which version of the server the session is connected to. + * @return The server version of null if not available. + */ + String server(); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/summary/ResultSummary.java b/driver/src/main/java/org/neo4j/driver/v1/summary/ResultSummary.java index 9108943900..a3128c2e5a 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/summary/ResultSummary.java +++ b/driver/src/main/java/org/neo4j/driver/v1/summary/ResultSummary.java @@ -19,9 +19,10 @@ package org.neo4j.driver.v1.summary; import java.util.List; +import java.util.concurrent.TimeUnit; -import org.neo4j.driver.v1.util.Immutable; import org.neo4j.driver.v1.Statement; +import org.neo4j.driver.v1.util.Immutable; /** * The result summary of running a statement. The result summary interface can be used to investigate @@ -90,4 +91,20 @@ public interface ResultSummary * notifications produced while executing the statement. */ List notifications(); + + /** + * The time it took the server to make the result available for consumption. + * + * @param unit The unit of the duration. + * @return The time it took for the server to have the result available in the provided time unit. + */ + long resultAvailableAfter( TimeUnit unit ); + + /** + * The time it took the server to consume the result. + * + * @param unit The unit of the duration. + * @return The time it took for the server to consume the result in the provided time unit. + */ + long resultConsumedAfter( TimeUnit unit ); } diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/SessionIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/SessionIT.java index 1796c6e075..c08ecc891f 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/SessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/SessionIT.java @@ -48,7 +48,7 @@ public class SessionIT public void shouldKnowSessionIsClosed() throws Throwable { // Given - try( Driver driver = GraphDatabase.driver( neo4j.uri() ); ) + try( Driver driver = GraphDatabase.driver( neo4j.uri() ) ) { Session session = driver.session(); diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/SummaryIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/SummaryIT.java index a84b3a44d8..b5a1ebd989 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/SummaryIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/SummaryIT.java @@ -22,6 +22,8 @@ import org.junit.Test; import java.util.List; +import java.util.concurrent.TimeUnit; + import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.Values; @@ -41,6 +43,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.neo4j.driver.v1.util.ServerVersion.v3_1_0; +import static org.neo4j.driver.v1.util.ServerVersion.version; public class SummaryIT { @@ -71,6 +75,27 @@ public void shouldContainBasicMetadata() throws Throwable assertFalse( summary.hasPlan() ); assertFalse( summary.hasProfile() ); assertThat( summary, equalTo( result.consume() ) ); + + } + + @Test + public void shouldContainTimeInformation() + { + // Given + ResultSummary summary = session.run( "UNWIND range(1,1000) AS n RETURN n AS number" ).consume(); + + // Then + if ( version( session.server() ).greaterThanOrEqual( v3_1_0 ) ) + { + assertThat( summary.resultAvailableAfter( TimeUnit.MILLISECONDS ), greaterThan( 0L ) ); + assertThat( summary.resultConsumedAfter( TimeUnit.MILLISECONDS ), greaterThan( 0L ) ); + } + else + { + //Not passed through by older versions of the server + assertThat( summary.resultAvailableAfter( TimeUnit.MILLISECONDS ), equalTo( -1L ) ); + assertThat( summary.resultConsumedAfter( TimeUnit.MILLISECONDS ), equalTo( -1L ) ); + } } @Test diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/ServerVersion.java b/driver/src/test/java/org/neo4j/driver/v1/util/ServerVersion.java new file mode 100644 index 0000000000..01fb05879e --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/util/ServerVersion.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.lang.Integer.compare; + +public class ServerVersion +{ + private final int major; + private final int minor; + private final int patch; + + private static final Pattern PATTERN = + Pattern.compile("Neo4j/(\\d+)\\.(\\d+)(?:\\.)?(\\d*)(\\.|-|\\+)?([0-9A-Za-z-.]*)?"); + + private ServerVersion( int major, int minor, int patch ) + { + this.major = major; + this.minor = minor; + this.patch = patch; + } + public static final ServerVersion v3_1_0 = new ServerVersion(3, 1, 0); + public static final ServerVersion v3_0_0 = new ServerVersion(3, 1, 0); + + public static ServerVersion version( String server ) + { + if ( server == null ) + { + return new ServerVersion( 3, 0, 0 ); + } + else + { + Matcher matcher = PATTERN.matcher( server ); + if ( matcher.matches() ) + { + int major = Integer.valueOf( matcher.group( 1 ) ); + int minor = Integer.valueOf( matcher.group( 2 ) ); + String patchString = matcher.group( 3 ); + int patch = 0; + if ( patchString != null && !patchString.isEmpty() ) + { + patch = Integer.valueOf( patchString ); + } + return new ServerVersion( major, minor, patch ); + } + else + { + throw new IllegalArgumentException( "Cannot parse " + server ); + } + } + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { return true; } + if ( o == null || getClass() != o.getClass() ) + { return false; } + + ServerVersion that = (ServerVersion) o; + + if ( major != that.major ) + { return false; } + if ( minor != that.minor ) + { return false; } + return patch == that.patch; + } + + @Override + public int hashCode() + { + int result = major; + result = 31 * result + minor; + result = 31 * result + patch; + return result; + } + + public boolean greaterThan(ServerVersion other) + { + return compareTo( other ) > 0; + } + + public boolean greaterThanOrEqual(ServerVersion other) + { + return compareTo( other ) >= 0; + } + + public boolean lessThan(ServerVersion other) + { + return compareTo( other ) < 0; + } + + public boolean lessThanOrEqual(ServerVersion other) + { + return compareTo( other ) <= 0; + } + + private int compareTo( ServerVersion o ) + { + int c = compare( major, o.major ); + if (c == 0) + { + c = compare( minor, o.minor ); + if (c == 0) + { + c = compare( patch, o.patch ); + } + } + + return c; + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4jSession.java b/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4jSession.java index 05a63501bd..8c3f648010 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4jSession.java +++ b/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4jSession.java @@ -24,11 +24,11 @@ import java.util.Map; import org.neo4j.driver.v1.Record; -import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.Transaction; -import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.TypeSystem; /** * A little utility for integration testing, this provides tests with a session they can work with. @@ -94,6 +94,12 @@ public void close() throw new UnsupportedOperationException( "Disallowed on this test session" ); } + @Override + public String server() + { + return realSession.server(); + } + @Override public Transaction beginTransaction() {