Skip to content
This repository has been archived by the owner on Jul 6, 2023. It is now read-only.

Commit

Permalink
Merge pull request #149 from henriknyman/1.2-upgrade-driver-alpha02
Browse files Browse the repository at this point in the history
Upgrade to driver version 2.0.0-alpha02
  • Loading branch information
henriknyman authored May 21, 2019
2 parents fd7b56d + 9412023 commit 90cf424
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 69 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ ext {
argparse4jVersion = '0.7.0'
junitVersion = '4.12'
evaluatorVersion = '3.5.4'
neo4jJavaDriverVersion = '2.0.0-alpha01'
neo4jJavaDriverVersion = '2.0.0-alpha02'
findbugsVersion = '3.0.0'
jansiVersion = '1.13'
jlineVersion = '2.14.6'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public void cypherWithOrder() throws CommandException {

//then
String actual = linePrinter.output();
assertThat( actual, containsString( "Ordered by" ) );
assertThat( actual, containsString( "Order" ) );
assertThat( actual, containsString( "n.age ASC" ) );
}

Expand Down
47 changes: 39 additions & 8 deletions cypher-shell/src/main/java/org/neo4j/shell/CypherShell.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.neo4j.cypher.internal.evaluator.EvaluationException;
import org.neo4j.cypher.internal.evaluator.Evaluator;
import org.neo4j.cypher.internal.evaluator.ExpressionEvaluator;
import org.neo4j.driver.exceptions.Neo4jException;
import org.neo4j.shell.commands.Command;
import org.neo4j.shell.commands.CommandExecutable;
import org.neo4j.shell.commands.CommandHelper;
Expand Down Expand Up @@ -37,6 +38,7 @@ public class CypherShell implements StatementExecuter, Connector, TransactionHan
private final PrettyPrinter prettyPrinter;
private CommandHelper commandHelper;
private ExpressionEvaluator evaluator = Evaluator.expressionEvaluator();
private String lastNeo4jErrorCode;

public CypherShell(@Nonnull LinePrinter linePrinter, @Nonnull PrettyConfig prettyConfig) {
this(linePrinter, new BoltStateHandler(), new PrettyPrinter(prettyConfig));
Expand Down Expand Up @@ -80,15 +82,26 @@ public void execute(@Nonnull final String cmdString) throws ExitException, Comma
executeCypher(cmdString);
}

@Override
public String lastNeo4jErrorCode() {
return lastNeo4jErrorCode;
}

/**
* Executes a piece of text as if it were Cypher. By default, all of the cypher is executed in single statement
* (with an implicit transaction).
*
* @param cypher non-empty cypher text to executeLine
*/
private void executeCypher(@Nonnull final String cypher) throws CommandException {
final Optional<BoltResult> result = boltStateHandler.runCypher(cypher, allParameterValues());
result.ifPresent(boltResult -> prettyPrinter.format(boltResult, linePrinter));
try {
final Optional<BoltResult> result = boltStateHandler.runCypher( cypher, allParameterValues() );
result.ifPresent(boltResult -> prettyPrinter.format(boltResult, linePrinter));
lastNeo4jErrorCode = null;
} catch (Neo4jException e) {
lastNeo4jErrorCode = e.code();
throw e;
}
}

@Override
Expand Down Expand Up @@ -142,9 +155,15 @@ public void beginTransaction() throws CommandException {

@Override
public Optional<List<BoltResult>> commitTransaction() throws CommandException {
Optional<List<BoltResult>> results = boltStateHandler.commitTransaction();
results.ifPresent(boltResult -> boltResult.forEach(result -> prettyPrinter.format(result, linePrinter)));
return results;
try {
Optional<List<BoltResult>> results = boltStateHandler.commitTransaction();
results.ifPresent(boltResult -> boltResult.forEach(result -> prettyPrinter.format(result, linePrinter)));
lastNeo4jErrorCode = null;
return results;
} catch (Neo4jException e) {
lastNeo4jErrorCode = e.code();
throw e;
}
}

@Override
Expand Down Expand Up @@ -202,12 +221,24 @@ protected void addRuntimeHookToResetShell() {
@Override
public void setActiveDatabase(String databaseName) throws CommandException
{
boltStateHandler.setActiveDatabase(databaseName);
try {
boltStateHandler.setActiveDatabase(databaseName);
lastNeo4jErrorCode = null;
} catch (Neo4jException e) {
lastNeo4jErrorCode = e.code();
throw e;
}
}

@Override
public String getActiveDatabaseAsSetByUser()
{
return boltStateHandler.getActiveDatabaseAsSetByUser();
}

@Override
public String getActiveDatabase()
public String getActualDatabaseAsReportedByServer()
{
return boltStateHandler.getActiveDatabase();
return boltStateHandler.getActualDatabaseAsReportedByServer();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
public interface DatabaseManager
{
String ABSENT_DB_NAME = "";
String DEFAULT_DEFAULT_DB_NAME = "neo4j";
String SYSTEM_DB_NAME = "system";
String DEFAULT_DEFAULT_DB_NAME = "neo4j";

String DATABASE_NOT_FOUND_ERROR_CODE = "Neo.ClientError.Database.DatabaseNotFound";
String DATABASE_UNAVAILABLE_ERROR_CODE = "Neo.TransientError.General.DatabaseUnavailable";

void setActiveDatabase(String databaseName) throws CommandException;

String getActiveDatabase();
String getActiveDatabaseAsSetByUser();

String getActualDatabaseAsReportedByServer();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ public interface StatementExecuter {
* Stops any running statements
*/
void reset();

/**
* Get the error code from the last executed Cypher statement, or null if the last execution was successful.
*/
String lastNeo4jErrorCode();
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME;
import static org.neo4j.shell.DatabaseManager.DEFAULT_DEFAULT_DB_NAME;
import static org.neo4j.shell.DatabaseManager.ABSENT_DB_NAME;
import static org.neo4j.shell.DatabaseManager.DATABASE_NOT_FOUND_ERROR_CODE;
import static org.neo4j.shell.DatabaseManager.DATABASE_UNAVAILABLE_ERROR_CODE;

/**
* A shell runner intended for interactive sessions where lines are input one by one and execution should happen
Expand All @@ -39,6 +40,10 @@ public class InteractiveShellRunner implements ShellRunner, SignalHandler {
private final static String TRANSACTION_PROMPT = "# ";
private final static String USERNAME_DB_DELIMITER = "@";
private final static int ONELINE_PROMPT_MAX_LENGTH = 50;
private static final String UNRESOLVED_DEFAULT_DB_PROPMPT_TEXT = "<default_database>";
private static final String DATABASE_NOT_FOUND_ERROR_PROMPT_TEXT = "[NOT_FOUND]";
private static final String DATABASE_UNAVAILABLE_ERROR_PROMPT_TEXT = "[UNAVAILABLE]";

// Need to know if we are currently executing when catch Ctrl-C, needs to be atomic due to
// being called from different thread
private final AtomicBoolean currentlyExecuting;
Expand Down Expand Up @@ -162,26 +167,35 @@ AnsiFormattedText updateAndGetPrompt() {
return continuationPrompt;
}

String databaseName = databaseManager.getActiveDatabase();
String databaseName = databaseManager.getActualDatabaseAsReportedByServer();
if (databaseName == null) {
// We have failed to get a successful response from the connection ping query
// Build the prompt from the db name as set by the user + a suffix indicating that we are in a disconnected state
String dbNameSetByUser = databaseManager.getActiveDatabaseAsSetByUser();
databaseName = ABSENT_DB_NAME.equals(dbNameSetByUser)? UNRESOLVED_DEFAULT_DB_PROPMPT_TEXT : dbNameSetByUser;
} else if (ABSENT_DB_NAME.equals(databaseName)) {
// The driver did not give us a database name in the response from the connection ping query
databaseName = UNRESOLVED_DEFAULT_DB_PROPMPT_TEXT;
}

// Substitute empty name for the default default-database-name
// For now we just use a hard-coded default name
// Ideally we would like to receive the actual name in the ResultSummary when we connect (in BoltStateHandler.reconnect())
// (If the user is an admin we could also query for the default database config value with:
// "CALL dbms.listConfig() YIELD name, value WHERE name = "dbms.default_database" RETURN value"
// but that does not work in general)
databaseName = ABSENT_DB_NAME.equals(databaseName) ? DEFAULT_DEFAULT_DB_NAME : databaseName;
String errorSuffix = getErrorPrompt(executer.lastNeo4jErrorCode());

int promptIndent = connectionConfig.username().length() +
USERNAME_DB_DELIMITER.length() +
databaseName.length() +
errorSuffix.length() +
FRESH_PROMPT.length();

AnsiFormattedText prePrompt = AnsiFormattedText.s().bold()
.append(connectionConfig.username())
.append("@")
.append(databaseName);

// If we encountered an error with the connection ping query we display it in the prompt in RED
if (!errorSuffix.isEmpty()) {
prePrompt.colorRed().append(errorSuffix).colorDefault();
}

if (promptIndent <= ONELINE_PROMPT_MAX_LENGTH) {
continuationPrompt = AnsiFormattedText.s().bold().append(OutputFormatter.repeat(' ', promptIndent));
return prePrompt
Expand All @@ -195,6 +209,19 @@ AnsiFormattedText updateAndGetPrompt() {
}
}

private String getErrorPrompt(String errorCode) {
// NOTE: errorCode can be null
String errorPromptSuffix;
if (DATABASE_NOT_FOUND_ERROR_CODE.equals(errorCode)) {
errorPromptSuffix = DATABASE_NOT_FOUND_ERROR_PROMPT_TEXT;
} else if (DATABASE_UNAVAILABLE_ERROR_CODE.equals(errorCode)) {
errorPromptSuffix = DATABASE_UNAVAILABLE_ERROR_PROMPT_TEXT;
} else {
errorPromptSuffix = "";
}
return errorPromptSuffix;
}

/**
* Catch Ctrl-C from user and handle it nicely
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.neo4j.driver.StatementResult;
import org.neo4j.driver.Transaction;
import org.neo4j.driver.exceptions.SessionExpiredException;
import org.neo4j.driver.summary.DatabaseInfo;
import org.neo4j.shell.ConnectionConfig;
import org.neo4j.shell.Connector;
import org.neo4j.shell.DatabaseManager;
Expand All @@ -36,18 +37,19 @@
public class BoltStateHandler implements TransactionHandler, Connector, DatabaseManager {
private final TriFunction<String, AuthToken, Config, Driver> driverProvider;
protected Driver driver;
protected Session session;
Session session;
private String version;
private List<Statement> transactionStatements;
private String activeDatabaseName;
private String activeDatabaseNameAsSetByUser;
private String actualDatabaseNameAsReportedByServer;

public BoltStateHandler() {
this(GraphDatabase::driver);
}

BoltStateHandler(TriFunction<String, AuthToken, Config, Driver> driverProvider) {
this.driverProvider = driverProvider;
activeDatabaseName = "";
activeDatabaseNameAsSetByUser = ABSENT_DB_NAME;
}

@Override
Expand All @@ -56,16 +58,22 @@ public void setActiveDatabase(String databaseName) throws CommandException
if (isTransactionOpen()) {
throw new CommandException("There is an open transaction. You need to close it before you can switch database.");
}
activeDatabaseName = databaseName;
activeDatabaseNameAsSetByUser = databaseName;
if (isConnected()) {
reconnect(false);
}
}

@Override
public String getActiveDatabase()
public String getActiveDatabaseAsSetByUser()
{
return activeDatabaseName;
return activeDatabaseNameAsSetByUser;
}

@Override
public String getActualDatabaseAsReportedByServer()
{
return actualDatabaseNameAsReportedByServer;
}

@Override
Expand Down Expand Up @@ -146,14 +154,21 @@ private void reconnect(boolean keepBookmark) {
session.close();
sessionOptionalArgs = t -> t.withBookmarks(bookmark);
}
Consumer<SessionParametersTemplate> sessionArgs = t -> t.withDefaultAccessMode(AccessMode.WRITE).withDatabase(activeDatabaseName);
Consumer<SessionParametersTemplate> sessionArgs = t -> {
t.withDefaultAccessMode(AccessMode.WRITE);
if (!ABSENT_DB_NAME.equals(activeDatabaseNameAsSetByUser)) {
t.withDatabase(activeDatabaseNameAsSetByUser);
}
};
session = driver.session(sessionArgs.andThen(sessionOptionalArgs));

String query = activeDatabaseName.equals(SYSTEM_DB_NAME) ? "SHOW DATABASES" : "RETURN 1";
String query = activeDatabaseNameAsSetByUser.equals(SYSTEM_DB_NAME) ? "SHOW DATABASES" : "RETURN 1";

resetActualDbName(); // Set this to null first in case run throws an exception
StatementResult run = session.run(query);

this.version = run.summary().server().version();
// It would be nice if we could also get the actual database name here, in the case where we used ABSENT_DB_NAME
run.consume();
updateActualDbName(run);
}

@Nonnull
Expand Down Expand Up @@ -207,9 +222,24 @@ private Optional<BoltResult> getBoltResult(@Nonnull String cypher, @Nonnull Map<
return Optional.empty();
}

updateActualDbName(statementResult);

return Optional.of(new StatementBoltResult(statementResult));
}

private String getActualDbName(@Nonnull StatementResult statementResult) {
DatabaseInfo dbInfo = statementResult.summary().database();
return dbInfo.name() == null ? ABSENT_DB_NAME : dbInfo.name();
}

private void updateActualDbName(@Nonnull StatementResult statementResult) {
actualDatabaseNameAsReportedByServer = getActualDbName(statementResult);
}

private void resetActualDbName() {
actualDatabaseNameAsReportedByServer = null;
}

/**
* Disconnect from Neo4j, clearing up any session resources, but don't give any output.
* Intended only to be used if connect fails.
Expand All @@ -225,6 +255,7 @@ void silentDisconnect() {
} finally {
session = null;
driver = null;
resetActualDbName();
}
}

Expand Down Expand Up @@ -265,7 +296,9 @@ private Optional<List<BoltResult>> captureResults(@Nonnull List<Statement> trans
List<BoltResult> results = executeWithRetry(transactionStatements, (statement, transaction) -> {
// calling list() is what actually executes cypher on the server
StatementResult sr = transaction.run(statement);
return new ListBoltResult(sr.list(), sr.consume(), sr.keys());
BoltResult singleResult = new ListBoltResult(sr.list(), sr.consume(), sr.keys());
updateActualDbName(sr);
return singleResult;
});

clearTransactionStatements();
Expand Down
Loading

0 comments on commit 90cf424

Please sign in to comment.