Skip to content
This repository was archived by the owner on Jul 6, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
neo4jJavaVersion = '1.5.1'
neo4jJavaVersion = '1.6.0-beta01'
findbugsVersion = '3.0.0'
jansiVersion = '1.13'
jlineVersion = '2.14.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,22 @@ public void paramsAndListVariables() throws CommandException {
assertTrue(shell.getAll().isEmpty());

long randomLong = System.currentTimeMillis();
String stringInput = "\"randomString\"";
shell.set("string", stringInput);
Optional result = shell.set("bob", String.valueOf(randomLong));
assertTrue(result.isPresent());
assertEquals(randomLong, result.get());

shell.execute("RETURN { bob }");
shell.execute("RETURN { bob }, $string");

ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(logger).printOut(captor.capture());

List<String> queryResult = captor.getAllValues();
assertThat(queryResult.get(0), containsString("| { bob }"));
assertThat(queryResult.get(0), containsString("\n| " + randomLong+ " |\n"));
assertThat(queryResult.get(0), containsString("| " + randomLong + " | " + stringInput + " |"));
assertEquals(randomLong, shell.getAll().get("bob"));
assertEquals("randomString", shell.getAll().get("string"));
}

@Test
Expand Down
21 changes: 17 additions & 4 deletions cypher-shell/src/main/java/org/neo4j/shell/CypherShell.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,25 @@
import org.neo4j.shell.prettyprint.PrettyPrinter;
import org.neo4j.shell.state.BoltResult;
import org.neo4j.shell.state.BoltStateHandler;
import org.neo4j.shell.state.ParamValue;

import javax.annotation.Nonnull;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* A possibly interactive shell for evaluating cypher statements.
*/
public class CypherShell implements StatementExecuter, Connector, TransactionHandler, VariableHolder {
// Final space to catch newline
protected static final Pattern cmdNamePattern = Pattern.compile("^\\s*(?<name>[^\\s]+)\\b(?<args>.*)\\s*$");
protected final Map<String, Object> queryParams = new HashMap<>();
protected final Map<String, ParamValue> queryParams = new HashMap<>();
private final Logger logger;
private final BoltStateHandler boltStateHandler;
private final PrettyPrinter prettyPrinter;
Expand Down Expand Up @@ -80,7 +83,7 @@ public void execute(@Nonnull final String cmdString) throws ExitException, Comma
* @param cypher non-empty cypher text to executeLine
*/
protected void executeCypher(@Nonnull final String cypher) throws CommandException {
final Optional<BoltResult> result = boltStateHandler.runCypher(cypher, queryParams);
final Optional<BoltResult> result = boltStateHandler.runCypher(cypher, getAll());
result.ifPresent(boltResult -> logger.printOut(prettyPrinter.format(boltResult)));
}

Expand Down Expand Up @@ -156,13 +159,13 @@ public Optional set(@Nonnull String name, @Nonnull String valueString) throws Co
final BoltResult result = setParamsAndValidate(name, valueString);
String parameterName = CypherVariablesFormatter.unescapedCypherVariable(name);
final Object value = result.getRecords().get(0).get(parameterName).asObject();
queryParams.put(parameterName, value);
queryParams.put(parameterName, new ParamValue(valueString, value));
return Optional.ofNullable(value);
}

private BoltResult setParamsAndValidate(@Nonnull String name, @Nonnull String valueString) throws CommandException {
String cypher = "RETURN " + valueString + " as " + name;
final Optional<BoltResult> result = boltStateHandler.runCypher(cypher, queryParams);
final Optional<BoltResult> result = boltStateHandler.runCypher(cypher, getAll());
if (!result.isPresent() || result.get().getRecords().isEmpty()) {
throw new CommandException("Failed to set value of parameter");
}
Expand All @@ -172,6 +175,16 @@ private BoltResult setParamsAndValidate(@Nonnull String name, @Nonnull String va
@Override
@Nonnull
public Map<String, Object> getAll() {
return queryParams.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
value -> value.getValue().getValue()));
}

@Nonnull
@Override
public Map<String, ParamValue> getAllAsUserInput() {
return queryParams;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.neo4j.shell;

import org.neo4j.shell.exception.CommandException;
import org.neo4j.shell.state.ParamValue;

import javax.annotation.Nonnull;
import java.util.Map;
Expand All @@ -22,4 +23,10 @@ public interface VariableHolder {
*/
@Nonnull
Map<String, Object> getAll();

/**
* @return map of all currently set variables and their values corresponding to the user valueString
*/
@Nonnull
Map<String, ParamValue> getAllAsUserInput();
}
49 changes: 38 additions & 11 deletions cypher-shell/src/main/java/org/neo4j/shell/commands/Param.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -17,7 +18,10 @@
public class Param implements Command {
// Match arguments such as "(key) (value with possible spaces)" where key and value are any strings
private static final Pattern backtickPattern = Pattern.compile("^\\s*(?<key>(`([^`])*`)+?):?\\s+(?<value>.+)$");
private static final Pattern backtickLambdaPattern = Pattern.compile("^\\s*(?<key>(`([^`])*`)+?)\\s*=>\\s*(?<value>.+)$");
private static final Pattern argPattern = Pattern.compile("^\\s*(?<key>[\\p{L}_][\\p{L}0-9_]*):?\\s+(?<value>.+)$");
private static final Pattern lambdaPattern = Pattern.compile("^\\s*(?<key>[\\p{L}_][\\p{L}0-9_]*)\\s*=>\\s*(?<value>.+)$");
private static final Pattern lambdaMapPattern = Pattern.compile("^\\s*(?<key>[\\p{L}_][\\p{L}0-9_]*):\\s*=>\\s*(?<value>.+)$");

public static final String COMMAND_NAME = ":param";
private final VariableHolder variableHolder;
Expand All @@ -41,7 +45,7 @@ public String getDescription() {
@Nonnull
@Override
public String getUsage() {
return "name value";
return "name => value";
}

@Nonnull
Expand All @@ -58,21 +62,44 @@ public List<String> getAliases() {

@Override
public void execute(@Nonnull final String argString) throws CommandException {
Matcher alphanumericMatcher = argPattern.matcher(argString);
if (alphanumericMatcher.matches()) {
variableHolder.set(alphanumericMatcher.group("key"), alphanumericMatcher.group("value"));
} else {
checkForBackticks(argString);
Matcher lambdaMapMatcher = lambdaMapPattern.matcher(argString);
if (lambdaMapMatcher.matches()) {
throw new CommandException(AnsiFormattedText.from("Incorrect usage.\nusage: ")
.bold().append(COMMAND_NAME).boldOff().append(" ").append(getUsage()));
}
if (!assignIfValidParameter(argString)) {
throw new CommandException(AnsiFormattedText.from("Incorrect number of arguments.\nusage: ")
.bold().append(COMMAND_NAME).boldOff().append(" ").append(getUsage()));
}
}

private boolean assignIfValidParameter(@Nonnull String argString) throws CommandException {
return setParameterIfItMatchesPattern(argString, lambdaPattern, assignIfValidParameter())
|| setParameterIfItMatchesPattern(argString, argPattern, assignIfValidParameter())
|| setParameterIfItMatchesPattern(argString, backtickLambdaPattern, backTickMatchPattern())
|| setParameterIfItMatchesPattern(argString, backtickPattern, backTickMatchPattern());
}

private void checkForBackticks(@Nonnull String argString) throws CommandException {
Matcher matcher = backtickPattern.matcher(argString);
if (argString.trim().startsWith("`") && matcher.matches() && matcher.group("key").length() > 2) {
private boolean setParameterIfItMatchesPattern(@Nonnull String argString, Pattern pattern,
BiPredicate<String, Matcher> matchingFunction) throws CommandException {
Matcher matcher = pattern.matcher(argString);
if (matchingFunction.test(argString, matcher)) {
variableHolder.set(matcher.group("key"), matcher.group("value"));
return true;
} else {
throw new CommandException(AnsiFormattedText.from("Incorrect number of arguments.\nusage: ")
.bold().append(COMMAND_NAME).boldOff().append(" ").append(getUsage()));
return false;
}
}

private BiPredicate<String, Matcher> assignIfValidParameter() {
return (argString, matcher) -> matcher.matches();
}

private BiPredicate<String, Matcher> backTickMatchPattern() {
return (argString, backtickLambdaMatcher) -> {
return argString.trim().startsWith("`")
&& backtickLambdaMatcher.matches()
&& backtickLambdaMatcher.group("key").length() > 2;
};
}
}
10 changes: 5 additions & 5 deletions cypher-shell/src/main/java/org/neo4j/shell/commands/Params.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,21 @@ public void execute(@Nonnull final String argString) throws ExitException, Comma

private void listParam(@Nonnull String name) throws CommandException {
String parameterName = CypherVariablesFormatter.unescapedCypherVariable(name);
if (!variableHolder.getAll().containsKey(parameterName)) {
if (!this.variableHolder.getAllAsUserInput().containsKey(parameterName)) {
throw new CommandException("Unknown parameter: " + name);
}
listParam(name.length(), name, variableHolder.getAll().get(parameterName));
listParam(name.length(), name, this.variableHolder.getAllAsUserInput().get(parameterName).getValueAsString());
}

private void listParam(int leftColWidth, @Nonnull String key, @Nonnull Object value) {
logger.printOut(String.format("%-" + leftColWidth + "s: %s", key, value));
logger.printOut(String.format(":param %-" + leftColWidth + "s => %s", key, value));
}

private void listAllParams() {
List<String> keys = variableHolder.getAll().keySet().stream().sorted().collect(Collectors.toList());
List<String> keys = variableHolder.getAllAsUserInput().keySet().stream().sorted().collect(Collectors.toList());

int leftColWidth = keys.stream().map((s) -> escape(s).length()).reduce(0, Math::max);

keys.stream().forEach(k -> listParam(leftColWidth, escape(k), variableHolder.getAll().get(k)));
keys.forEach(key -> listParam(leftColWidth, escape(key), variableHolder.getAllAsUserInput().get(key).getValueAsString()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
import org.neo4j.driver.v1.types.Relationship;
import org.neo4j.shell.state.BoltResult;

import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.Arrays.asList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
Expand All @@ -31,23 +35,23 @@ public interface OutputFormatter {
@Nonnull default String formatValue(@Nonnull final Value value) {
TypeRepresentation type = (TypeRepresentation) value.type();
switch (type.constructor()) {
case LIST_TyCon:
case LIST:
return listAsString(value.asList(this::formatValue));
case MAP_TyCon:
case MAP:
return mapAsString(value.asMap(this::formatValue));
case NODE_TyCon:
case NODE:
return nodeAsString(value.asNode());
case RELATIONSHIP_TyCon:
case RELATIONSHIP:
return relationshipAsString(value.asRelationship());
case PATH_TyCon:
case PATH:
return pathAsString(value.asPath());
case ANY_TyCon:
case BOOLEAN_TyCon:
case STRING_TyCon:
case NUMBER_TyCon:
case INTEGER_TyCon:
case FLOAT_TyCon:
case NULL_TyCon:
case ANY:
case BOOLEAN:
case STRING:
case NUMBER:
case INTEGER:
case FLOAT:
case NULL:
default:
return value.toString();
}
Expand Down
22 changes: 22 additions & 0 deletions cypher-shell/src/main/java/org/neo4j/shell/state/ParamValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.neo4j.shell.state;

/**
* Handles queryparams value and user inputString
*/
public class ParamValue {
private final String valueAsString;
private final Object value;

public ParamValue(String valueAsString, Object value) {
this.valueAsString = valueAsString;
this.value = value;
}

public Object getValue() {
return value;
}

public String getValueAsString() {
return valueAsString;
}
}
44 changes: 41 additions & 3 deletions cypher-shell/src/test/java/org/neo4j/shell/commands/ParamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.neo4j.shell.VariableHolder;
import org.neo4j.shell.exception.CommandException;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.fail;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -44,12 +45,26 @@ public void shouldFailIfOneArg() throws CommandException {
}

@Test
public void setValue() throws CommandException {
public void setParam() throws CommandException {
cmd.execute("bob 9");

verify(mockShell).set("bob", "9");
}

@Test
public void setLambdasAsParam() throws CommandException {
cmd.execute("bob => 9");

verify(mockShell).set("bob", "9");
}

@Test
public void setLambdasAsParamWithBackticks() throws CommandException {
cmd.execute("`bob` => 9");

verify(mockShell).set("`bob`", "9");
}

@Test
public void setSpecialCharacterParameter() throws CommandException {
cmd.execute("bØb 9");
Expand All @@ -58,14 +73,21 @@ public void setSpecialCharacterParameter() throws CommandException {
}

@Test
public void setValueWithSpecialCharacters() throws CommandException {
public void setSpecialCharacterParameterForLambdaExpressions() throws CommandException {
cmd.execute("`first=>Name` => \"Bruce\"");

verify(mockShell).set("`first=>Name`", "\"Bruce\"");
}

@Test
public void setParamWithSpecialCharacters() throws CommandException {
cmd.execute("`bob#` 9");

verify(mockShell).set("`bob#`", "9");
}

@Test
public void setValueWithOddNoOfBackTicks() throws CommandException {
public void setParamWithOddNoOfBackTicks() throws CommandException {
cmd.execute(" `bo `` sömething ``` 9");

verify(mockShell).set("`bo `` sömething ```", "9");
Expand All @@ -81,6 +103,16 @@ public void shouldFailForVariablesWithoutEscaping() throws CommandException {
fail("Expected error");
}

@Test
public void shouldFailForVariablesMixingMapStyleAssignmentAndLambdas() throws CommandException {
thrown.expect(CommandException.class);
thrown.expectMessage(containsString("Incorrect usage"));

cmd.execute("bob: => 9");

fail("Expected error");
}

@Test
public void shouldFailForEmptyVariables() throws CommandException {
thrown.expect(CommandException.class);
Expand Down Expand Up @@ -143,4 +175,10 @@ public void shouldNotExecuteEscapedCypher() throws CommandException {
cmd.execute("bob \"RETURN 5 as bob\"");
verify(mockShell).set("bob", "\"RETURN 5 as bob\"");
}

@Test
public void printUsage() throws CommandException {
String usage = cmd.getUsage();
assertEquals(usage, "name => value");
}
}
Loading