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

Commit 88f54d9

Browse files
Render Details column in plan table
1 parent 58c532d commit 88f54d9

File tree

3 files changed

+101
-3
lines changed

3 files changed

+101
-3
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,21 @@ 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+
assumeTrue((minorVersion(serverVersion) > 0 && majorVersion(serverVersion) == 4) || majorVersion(serverVersion) > 4);
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( "Details" ) );
217+
assertThat( actual, containsString( "n.age AS age" ) );
218+
}
219+
205220
@Test
206221
public void cypherWithExplainAndRulePlanner() throws CommandException {
207222
//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";
@@ -40,14 +40,16 @@ class TablePlanFormatter {
4040
private static final String MEMORY = "Memory (Bytes)";
4141
private static final String IDENTIFIERS = "Identifiers";
4242
private static final String OTHER = "Other";
43+
private 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) {
@@ -161,6 +163,8 @@ private String serialize(@Nonnull String key, @Nonnull Value v) {
161163
return v.asString();
162164
case "PageCacheMisses":
163165
return v.asNumber().toString();
166+
case "Details":
167+
return v.asString();
164168
default:
165169
return v.asObject().toString();
166170
}
@@ -208,6 +212,8 @@ private Map<String, Justified> details(@Nonnull Plan plan, @Nonnull Map<String,
208212
return mapping(TIME, new Right(String.format("%.3f", value.asLong() / 1000000.0d)), columns);
209213
case "Order":
210214
return mapping( ORDER, new Left( String.format( "%s", value.asString() ) ), columns );
215+
case "Details":
216+
return mapping( DETAILS, new Left( truncate(value.asString()) ), columns );
211217
case "Memory":
212218
return mapping( MEMORY, new Right( String.format( "%s", value.asNumber().toString() ) ), columns );
213219
default:
@@ -439,4 +445,12 @@ public static <T1, T2> Pair<T1, T2> of(T1 _1, T2 _2) {
439445
return new Pair<>(_1, _2);
440446
}
441447
}
448+
449+
private String truncate( String original ) {
450+
if(original.length() <= MAX_DETAILS_COLUMN_WIDTH){
451+
return original;
452+
}
453+
454+
return original.substring( 0, MAX_DETAILS_COLUMN_WIDTH - 3 ) + "...";
455+
}
442456
}
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)