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

Commit 09ce816

Browse files
Merge pull request #221 from LinneaAndersson/4.1-render-details-column
4.1 render details column in plan details in cypher shell
2 parents 58c532d + 01719f1 commit 09ce816

File tree

3 files changed

+123
-8
lines changed

3 files changed

+123
-8
lines changed

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
import org.neo4j.shell.cli.Format;
1414
import org.neo4j.shell.exception.CommandException;
1515
import org.neo4j.shell.prettyprint.PrettyConfig;
16+
import org.neo4j.shell.prettyprint.TablePlanFormatter;
1617

1718
import static org.hamcrest.CoreMatchers.containsString;
18-
import static org.hamcrest.CoreMatchers.equalTo;
1919
import static org.hamcrest.CoreMatchers.not;
2020
import static org.hamcrest.MatcherAssert.assertThat;
2121
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -202,6 +202,37 @@ public void cypherWithOrder() throws CommandException {
202202
assertThat( actual, containsString( "n.age ASC" ) );
203203
}
204204

205+
@Test
206+
public void cypherWithQueryDetails() throws CommandException {
207+
// given
208+
String serverVersion = shell.getServerVersion();
209+
assumeThat( version(serverVersion), greaterThanOrEqualTo(version("4.1")));
210+
211+
//when
212+
shell.execute("EXPLAIN MATCH (n) with n.age AS age RETURN age");
213+
214+
//then
215+
String actual = linePrinter.output();
216+
assertThat( actual, containsString( TablePlanFormatter.DETAILS ) );
217+
assertThat( actual, containsString( "n.age AS age" ) );
218+
assertThat( actual, not( containsString( TablePlanFormatter.IDENTIFIERS ) ) );
219+
}
220+
221+
@Test
222+
public void cypherWithoutQueryDetails() throws CommandException {
223+
// given
224+
String serverVersion = shell.getServerVersion();
225+
assumeThat( version(serverVersion), not(greaterThanOrEqualTo(version("4.1"))));
226+
227+
//when
228+
shell.execute("EXPLAIN MATCH (n) with n.age AS age RETURN age");
229+
230+
//then
231+
String actual = linePrinter.output();
232+
assertThat( actual, not( containsString( TablePlanFormatter.DETAILS ) ) );
233+
assertThat( actual, containsString( TablePlanFormatter.IDENTIFIERS ) );
234+
}
235+
205236
@Test
206237
public void cypherWithExplainAndRulePlanner() throws CommandException {
207238
//given (there is no rule planner in neo4j 4.0)
@@ -232,7 +263,7 @@ public void cypherWithProfileWithMemory() throws CommandException {
232263
//then
233264
String actual = linePrinter.output();
234265
assertThat(actual, containsString("| Plan | Statement | Version | Planner | Runtime | Time | DbHits | Rows | Memory (Bytes) |")); // First table
235-
assertThat(actual, containsString("| Operator | Estimated Rows | Rows | DB Hits | Cache H/M | Memory (Bytes) | Identifiers |")); // Second table
266+
assertThat(actual, containsString("| Operator | Details | Estimated Rows | Rows | DB Hits | Cache H/M | Memory (Bytes) |")); // Second table
236267
}
237268

238269
@Test

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
import static org.neo4j.shell.prettyprint.OutputFormatter.NEWLINE;
2727
import static org.neo4j.shell.prettyprint.OutputFormatter.repeat;
2828

29-
class TablePlanFormatter {
29+
public 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";
@@ -38,16 +38,18 @@ class TablePlanFormatter {
3838
private static final String TIME = "Time (ms)";
3939
private static final String ORDER = "Ordered by";
4040
private static final String MEMORY = "Memory (Bytes)";
41-
private static final String IDENTIFIERS = "Identifiers";
41+
public static final String IDENTIFIERS = "Identifiers";
4242
private static final String OTHER = "Other";
43+
public static final String DETAILS = "Details";
4344
private static final String SEPARATOR = ", ";
4445
private static final Pattern DEDUP_PATTERN = Pattern.compile("\\s*(\\S+)@\\d+");
46+
public static final int MAX_DETAILS_COLUMN_WIDTH = 100;
4547

46-
private static final List<String> HEADERS = asList(OPERATOR, ESTIMATED_ROWS, ROWS, HITS, PAGE_CACHE, TIME, MEMORY, IDENTIFIERS, ORDER, OTHER);
48+
private static final List<String> HEADERS = asList(OPERATOR, DETAILS, ESTIMATED_ROWS, ROWS, HITS, PAGE_CACHE, TIME, MEMORY, IDENTIFIERS, ORDER, OTHER);
4749

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

5355
private int width(@Nonnull String header, @Nonnull Map<String, Integer> columns) {
@@ -79,7 +81,8 @@ String formatPlan(@Nonnull Plan plan) {
7981
Map<String, Integer> columns = new HashMap<>();
8082
List<Line> lines = accumulate(plan, new Root(), columns);
8183

82-
List<String> headers = HEADERS.stream().filter(columns::containsKey).collect(Collectors.toList());
84+
// Remove Identifiers column if we have a Details column
85+
List<String> headers = HEADERS.stream().filter(header -> columns.containsKey(header) && !(header.equals(IDENTIFIERS) && columns.containsKey(DETAILS))).collect(Collectors.toList());
8386

8487
StringBuilder result = new StringBuilder((2 + NEWLINE.length() + headers.stream().mapToInt(h -> width(h, columns)).sum()) * (lines.size() * 2 + 3));
8588

@@ -161,6 +164,8 @@ private String serialize(@Nonnull String key, @Nonnull Value v) {
161164
return v.asString();
162165
case "PageCacheMisses":
163166
return v.asNumber().toString();
167+
case "Details":
168+
return v.asString();
164169
default:
165170
return v.asObject().toString();
166171
}
@@ -208,6 +213,8 @@ private Map<String, Justified> details(@Nonnull Plan plan, @Nonnull Map<String,
208213
return mapping(TIME, new Right(String.format("%.3f", value.asLong() / 1000000.0d)), columns);
209214
case "Order":
210215
return mapping( ORDER, new Left( String.format( "%s", value.asString() ) ), columns );
216+
case "Details":
217+
return mapping( DETAILS, new Left( truncate(value.asString()) ), columns );
211218
case "Memory":
212219
return mapping( MEMORY, new Right( String.format( "%s", value.asNumber().toString() ) ), columns );
213220
default:
@@ -439,4 +446,12 @@ public static <T1, T2> Pair<T1, T2> of(T1 _1, T2 _2) {
439446
return new Pair<>(_1, _2);
440447
}
441448
}
449+
450+
private String truncate( String original ) {
451+
if(original.length() <= MAX_DETAILS_COLUMN_WIDTH){
452+
return original;
453+
}
454+
455+
return original.substring( 0, MAX_DETAILS_COLUMN_WIDTH - 3 ) + "...";
456+
}
442457
}
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)