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

Commit 9e66a84

Browse files
Render Details column in plan table
1 parent e78b0ab commit 9e66a84

File tree

3 files changed

+102
-4
lines changed

3 files changed

+102
-4
lines changed

cypher-shell/src/integration-test/java/org/neo4j/shell/commands/CypherShellVerboseIntegrationTest.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public void paramsAndListVariablesWithSpecialCharacters() throws EvaluationExcep
192192
public void cypherWithOrder() throws CommandException {
193193
// given
194194
String serverVersion = shell.getServerVersion();
195-
assumeTrue(minorVersion(serverVersion) == 6 || majorVersion(serverVersion) == 4);
195+
assumeTrue((minorVersion(serverVersion) == 6 && majorVersion(serverVersion) == 3) || majorVersion(serverVersion) > 3);
196196

197197
shell.execute( "CREATE INDEX ON :Person(age)" );
198198
shell.execute( "CALL db.awaitIndexes()" );
@@ -206,6 +206,21 @@ public void cypherWithOrder() throws CommandException {
206206
assertThat( actual, containsString( "n.age ASC" ) );
207207
}
208208

209+
@Test
210+
public void cypherWithQueryDetails() throws CommandException {
211+
// given
212+
String serverVersion = shell.getServerVersion();
213+
assumeTrue((minorVersion(serverVersion) > 0 && majorVersion(serverVersion) == 4) || majorVersion(serverVersion) > 4);
214+
215+
//when
216+
shell.execute("EXPLAIN MATCH (n) with n.age AS age RETURN age");
217+
218+
//then
219+
String actual = linePrinter.output();
220+
assertThat( actual, containsString( "Details" ) );
221+
assertThat( actual, containsString( "n.age AS age" ) );
222+
}
223+
209224
@Test
210225
public void cypherWithExplainAndRulePlanner() throws CommandException {
211226
//given (there is no rule planner in neo4j 4.0)

cypher-shell/src/main/java/org/neo4j/shell/prettyprint/TablePlanFormatter.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
class TablePlanFormatter {
3030

31-
private static final String UNNAMED_PATTERN_STRING = " (UNNAMED|FRESHID|AGGREGATION)(\\d+)";
31+
private static final String UNNAMED_PATTERN_STRING = " (UNNAMED|FRESHID|AGGREGATION|NODE|REL)(\\d+)";
3232
private static final Pattern UNNAMED_PATTERN = Pattern.compile(UNNAMED_PATTERN_STRING);
3333
private static final String OPERATOR = "Operator";
3434
private static final String ESTIMATED_ROWS = "Estimated Rows";
@@ -39,14 +39,16 @@ class TablePlanFormatter {
3939
private static final String ORDER = "Ordered by";
4040
private static final String IDENTIFIERS = "Identifiers";
4141
private static final String OTHER = "Other";
42+
private static final String DETAILS = "Details";
4243
private static final String SEPARATOR = ", ";
4344
private static final Pattern DEDUP_PATTERN = Pattern.compile("\\s*(\\S+)@\\d+");
45+
public static final int MAX_DETAILS_COLUMN_WIDTH = 100;
4446

45-
private static final List<String> HEADERS = asList(OPERATOR, ESTIMATED_ROWS, ROWS, HITS, PAGE_CACHE, TIME, IDENTIFIERS, ORDER, OTHER);
47+
private static final List<String> HEADERS = asList(OPERATOR, DETAILS, ESTIMATED_ROWS, ROWS, HITS, PAGE_CACHE, TIME, IDENTIFIERS, ORDER, OTHER);
4648

4749
private static final Set<String> IGNORED_ARGUMENTS = new LinkedHashSet<>(
4850
asList( "Rows", "DbHits", "EstimatedRows", "planner", "planner-impl", "planner-version", "version", "runtime", "runtime-impl", "runtime-version",
49-
"time", "source-code", "PageCacheMisses", "PageCacheHits", "PageCacheHitRatio", "Order" ) );
51+
"time", "source-code", "PageCacheMisses", "PageCacheHits", "PageCacheHitRatio", "Order", "Details" ) );
5052
public static final Value ZERO_VALUE = Values.value(0);
5153

5254
private int width(@Nonnull String header, @Nonnull Map<String, Integer> columns) {
@@ -160,6 +162,8 @@ private String serialize(@Nonnull String key, @Nonnull Value v) {
160162
return v.asString();
161163
case "PageCacheMisses":
162164
return v.asNumber().toString();
165+
case "Details":
166+
return v.asString();
163167
default:
164168
return v.asObject().toString();
165169
}
@@ -207,6 +211,8 @@ private Map<String, Justified> details(@Nonnull Plan plan, @Nonnull Map<String,
207211
return mapping(TIME, new Right(String.format("%.3f", value.asLong() / 1000000.0d)), columns);
208212
case "Order":
209213
return mapping( ORDER, new Left( String.format( "%s", value.asString() ) ), columns );
214+
case "Details":
215+
return mapping( DETAILS, new Left( truncate(value.asString()) ), columns );
210216
default:
211217
return Optional.empty();
212218
}
@@ -436,4 +442,12 @@ public static <T1, T2> Pair<T1, T2> of(T1 _1, T2 _2) {
436442
return new Pair<>(_1, _2);
437443
}
438444
}
445+
446+
private String truncate( String original ) {
447+
if(original.length() <= MAX_DETAILS_COLUMN_WIDTH){
448+
return original;
449+
}
450+
451+
return original.substring( 0, MAX_DETAILS_COLUMN_WIDTH - 3 ) + "...";
452+
}
439453
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.neo4j.shell.prettyprint;
2+
3+
import org.junit.Test;
4+
5+
import java.util.Collections;
6+
import java.util.Map;
7+
8+
import org.neo4j.driver.Value;
9+
import org.neo4j.driver.internal.value.StringValue;
10+
import org.neo4j.driver.summary.Plan;
11+
12+
import static org.hamcrest.CoreMatchers.containsString;
13+
import static org.hamcrest.CoreMatchers.is;
14+
import static org.hamcrest.MatcherAssert.assertThat;
15+
import static org.mockito.Mockito.mock;
16+
import static org.mockito.Mockito.when;
17+
import static org.neo4j.shell.prettyprint.OutputFormatter.NEWLINE;
18+
19+
public class TablePlanFormatterTest
20+
{
21+
TablePlanFormatter tablePlanFormatter = new TablePlanFormatter();
22+
23+
@Test
24+
public void renderShortDetails() {
25+
Plan plan = mock(Plan.class);
26+
Map<String, Value> args = Collections.singletonMap("Details", new StringValue("x.prop AS prop"));
27+
when(plan.arguments()).thenReturn(args);
28+
when(plan.operatorType()).thenReturn("Projection");
29+
30+
assertThat(tablePlanFormatter.formatPlan( plan ), is(String.join(NEWLINE,
31+
"+-------------+----------------+",
32+
"| Operator | Details |",
33+
"+-------------+----------------+",
34+
"| +Projection | x.prop AS prop |",
35+
"+-------------+----------------+", "")));
36+
}
37+
38+
@Test
39+
public void renderExactMaxLengthDetails() {
40+
Plan plan = mock(Plan.class);
41+
String details = stringOfLength(TablePlanFormatter.MAX_DETAILS_COLUMN_WIDTH);
42+
Map<String, Value> args = Collections.singletonMap("Details", new StringValue(details));
43+
when(plan.arguments()).thenReturn(args);
44+
when(plan.operatorType()).thenReturn("Projection");
45+
46+
assertThat(tablePlanFormatter.formatPlan( plan ), containsString("| +Projection | " + details + " |"));
47+
}
48+
49+
@Test
50+
public void truncateTooLongDetails() {
51+
Plan plan = mock(Plan.class);
52+
String details = stringOfLength(TablePlanFormatter.MAX_DETAILS_COLUMN_WIDTH + 1);
53+
Map<String, Value> args = Collections.singletonMap("Details", new StringValue(details));
54+
when(plan.arguments()).thenReturn(args);
55+
when(plan.operatorType()).thenReturn("Projection");
56+
57+
assertThat(tablePlanFormatter.formatPlan( plan ), containsString("| +Projection | " + details.substring( 0, TablePlanFormatter.MAX_DETAILS_COLUMN_WIDTH - 3 ) + "... |"));
58+
}
59+
60+
private String stringOfLength(int length) {
61+
StringBuilder strBuilder = new StringBuilder();
62+
63+
for(int i=0; i<length; i++) {
64+
strBuilder.append('a');
65+
}
66+
67+
return strBuilder.toString();
68+
}
69+
}

0 commit comments

Comments
 (0)