@@ -56,16 +56,16 @@ private int width(@Nonnull String header, @Nonnull Map<String, Integer> columns)
5656 return 2 + Math .max (header .length (), columns .get (header ));
5757 }
5858
59- private void pad (int width , char chr , @ Nonnull StringBuilder result ) {
59+ private static void pad (int width , char chr , @ Nonnull StringBuilder result ) {
6060 result .append (OutputFormatter .repeat (chr , width ));
6161 }
6262
6363
64- private void divider (@ Nonnull List <String > headers , @ Nullable Line line /*= null*/ , @ Nonnull StringBuilder result , @ Nonnull Map <String , Integer > columns ) {
64+ private void divider (@ Nonnull List <String > headers , @ Nullable TableRow tableRow /*= null*/ , @ Nonnull StringBuilder result , @ Nonnull Map <String , Integer > columns ) {
6565 for (String header : headers ) {
66- if (line != null && header .equals (OPERATOR ) && line .connection .isPresent ()) {
66+ if (tableRow != null && header .equals (OPERATOR ) && tableRow .connection .isPresent ()) {
6767 result .append ("|" );
68- String connection = line .connection .get ();
68+ String connection = tableRow .connection .get ();
6969 result .append (" " ).append (connection );
7070 pad (width (header , columns ) - connection .length () - 1 , ' ' , result );
7171 } else {
@@ -79,33 +79,34 @@ private void divider(@Nonnull List<String> headers, @Nullable Line line /*= null
7979 @ Nonnull
8080 String formatPlan (@ Nonnull Plan plan ) {
8181 Map <String , Integer > columns = new HashMap <>();
82- List <Line > lines = accumulate (plan , new Root (), columns );
82+ List <TableRow > tableRows = accumulate (plan , new Root (), columns );
8383
8484 // Remove Identifiers column if we have a Details column
8585 List <String > headers = HEADERS .stream ().filter (header -> columns .containsKey (header ) && !(header .equals (IDENTIFIERS ) && columns .containsKey (DETAILS ))).collect (Collectors .toList ());
8686
87- StringBuilder result = new StringBuilder ((2 + NEWLINE .length () + headers .stream ().mapToInt (h -> width (h , columns )).sum ()) * (lines .size () * 2 + 3 ));
88-
89- List <Line > allLines = new ArrayList <>();
90- Map <String , Justified > headerMap = headers .stream ().map (header -> Pair .of (header , new Left (header ))).collect (toMap (p -> p ._1 , p -> p ._2 ));
91- allLines .add (new Line (OPERATOR , headerMap , Optional .empty ()));
92- allLines .addAll (lines );
93- for (Line line : allLines ) {
94- divider (headers , line , result , columns );
95- for (String header : headers ) {
96- Justified detail = line .get (header );
97- result .append ("| " );
98- if (detail instanceof Left ) {
99- result .append (detail .text );
100- pad (width (header , columns ) - detail .length - 2 , ' ' , result );
87+ StringBuilder result = new StringBuilder ((2 + NEWLINE .length () + headers .stream ().mapToInt (h -> width (h , columns )).sum ()) * (tableRows .size () * 2 + 3 ));
88+
89+ List <TableRow > allTableRows = new ArrayList <>();
90+ Map <String ,Cell > headerMap = headers .stream ().map (header -> Pair .of (header , new LeftJustifiedCell (header ))).collect (toMap (p -> p ._1 , p -> p ._2 ));
91+ allTableRows .add (new TableRow (OPERATOR , headerMap , Optional .empty ()));
92+ allTableRows .addAll ( tableRows );
93+ for (int rowIndex = 0 ; rowIndex < allTableRows .size (); rowIndex ++) {
94+ TableRow tableRow = allTableRows .get ( rowIndex );
95+ divider (headers , tableRow , result , columns );
96+ for (int rowLineIndex = 0 ; rowLineIndex < tableRow .height ; rowLineIndex ++) {
97+ for (String header : headers ) {
98+ Cell cell = tableRow .get (header );
99+ String defaultText = "" ;
100+ if (header .equals (OPERATOR ) && rowIndex + 1 < allTableRows .size ()) {
101+ defaultText = allTableRows .get (rowIndex + 1 ).connection .orElse ("" ).replace ('\\' , ' ' );
102+ }
103+ result .append ( "| " );
104+ int columnWidth = width (header , columns );
105+ cell .writePaddedLine (rowLineIndex , defaultText , columnWidth , result );
106+ result .append ( " " );
101107 }
102- if (detail instanceof Right ) {
103- pad (width (header , columns ) - detail .length - 2 , ' ' , result );
104- result .append (detail .text );
105- }
106- result .append (" " );
108+ result .append ("|" ).append (NEWLINE );
107109 }
108- result .append ("|" ).append (NEWLINE );
109110 }
110111 divider (headers , null , result , columns );
111112
@@ -171,7 +172,7 @@ private String serialize(@Nonnull String key, @Nonnull Value v) {
171172 }
172173 }
173174
174- @ Nonnull private Stream <List <Line >> children (@ Nonnull Plan plan , Level level ,@ Nonnull Map <String , Integer > columns ) {
175+ @ Nonnull private Stream <List <TableRow >> children (@ Nonnull Plan plan , Level level , @ Nonnull Map <String , Integer > columns ) {
175176 List <? extends Plan > c = plan .children ();
176177 switch (c .size ()) {
177178 case 0 :
@@ -184,54 +185,55 @@ private String serialize(@Nonnull String key, @Nonnull Value v) {
184185 throw new IllegalStateException ("Plan has more than 2 children " + c );
185186 }
186187
187- @ Nonnull private List <Line > accumulate (@ Nonnull Plan plan , @ Nonnull Level level , @ Nonnull Map <String , Integer > columns ) {
188+ @ Nonnull private List <TableRow > accumulate (@ Nonnull Plan plan , @ Nonnull Level level , @ Nonnull Map <String , Integer > columns ) {
188189 String line = level .line () + plan .operatorType (); // wa plan.name
189- mapping (OPERATOR , new Left (line ), columns );
190+ mapping (OPERATOR , new LeftJustifiedCell (line ), columns );
190191
191192 return Stream .concat (
192- Stream .of (new Line (line , details (plan , columns ), level .connector ())),
193+ Stream .of (new TableRow (line , details (plan , columns ), level .connector ())),
193194 children (plan , level , columns ).flatMap (Collection ::stream ))
194195 .collect (Collectors .toList ());
195196 }
196197
197198 @ Nonnull
198- private Map <String , Justified > details (@ Nonnull Plan plan , @ Nonnull Map <String , Integer > columns ) {
199+ private Map <String ,Cell > details (@ Nonnull Plan plan , @ Nonnull Map <String , Integer > columns ) {
199200 Map <String , Value > args = plan .arguments ();
200201
201- Stream <Optional <Pair <String , Justified >>> formattedPlan = args .entrySet ().stream ().map (( e ) -> {
202+ Stream <Optional <Pair <String ,Cell >>> formattedPlan = args .entrySet ().stream ().map (e -> {
202203 Value value = e .getValue ();
203204 switch (e .getKey ()) {
204205 case "EstimatedRows" :
205- return mapping (ESTIMATED_ROWS , new Right (format (value .asDouble ())), columns );
206+ return mapping (ESTIMATED_ROWS , new RightJustifiedCell (format (value .asDouble ())), columns );
206207 case "Rows" :
207- return mapping (ROWS , new Right (value .asNumber ().toString ()), columns );
208+ return mapping (ROWS , new RightJustifiedCell (value .asNumber ().toString ()), columns );
208209 case "DbHits" :
209- return mapping (HITS , new Right (value .asNumber ().toString ()), columns );
210+ return mapping (HITS , new RightJustifiedCell (value .asNumber ().toString ()), columns );
210211 case "PageCacheHits" :
211- return mapping (PAGE_CACHE , new Right (String .format ("%s/%s" ,value .asNumber (),args .getOrDefault ("PageCacheMisses" , ZERO_VALUE ).asNumber ())), columns );
212+ return mapping (PAGE_CACHE , new RightJustifiedCell (String .format ("%s/%s" , value .asNumber (), args .getOrDefault ("PageCacheMisses" , ZERO_VALUE ).asNumber ())), columns );
212213 case "Time" :
213- return mapping (TIME , new Right (String .format ("%.3f" , value .asLong () / 1000000.0d )), columns );
214+ return mapping (TIME , new RightJustifiedCell (String .format ("%.3f" , value .asLong () / 1000000.0d )), columns );
214215 case "Order" :
215- return mapping ( ORDER , new Left ( String .format ( "%s" , value .asString () ) ), columns );
216+ return mapping (ORDER , new LeftJustifiedCell ( String .format ("%s" , value .asString ()) ), columns );
216217 case "Details" :
217- return mapping ( DETAILS , new Left ( truncate (value .asString ()) ), columns );
218+ return mapping (DETAILS , new LeftJustifiedCell ( splitDetails (value .asString ())), columns );
218219 case "Memory" :
219- return mapping ( MEMORY , new Right ( String .format ( "%s" , value .asNumber ().toString () ) ), columns );
220+ return mapping (MEMORY , new RightJustifiedCell ( String .format ("%s" , value .asNumber ().toString ()) ), columns );
220221 default :
221222 return Optional .empty ();
222223 }
223224 });
225+
224226 return Stream .concat (
225227 formattedPlan ,
226228 Stream .of (
227- Optional .of (Pair .of (IDENTIFIERS , new Left (identifiers (plan , columns )))),
228- Optional .of (Pair .of (OTHER , new Left (other (plan , columns ))))))
229- .filter (Optional ::isPresent )
230- .collect (toMap (o -> o .get ()._1 , o -> o .get ()._2 ));
229+ Optional .of (Pair .of (IDENTIFIERS , new LeftJustifiedCell (identifiers (plan , columns )))),
230+ Optional .of (Pair .of (OTHER , new LeftJustifiedCell (other (plan , columns ))))))
231+ .filter (Optional ::isPresent )
232+ .collect (toMap (o -> o .get ()._1 , o -> o .get ()._2 ));
231233 }
232234
233235 @ Nonnull
234- private Optional <Pair <String , Justified >> mapping (@ Nonnull String key , @ Nonnull Justified value , @ Nonnull Map <String , Integer > columns ) {
236+ private Optional <Pair <String ,Cell >> mapping (@ Nonnull String key , @ Nonnull Cell value , @ Nonnull Map <String , Integer > columns ) {
235237 update (columns , key , value .length );
236238 return Optional .of (Pair .of (key , value ));
237239 }
@@ -285,46 +287,75 @@ private String format(@Nonnull Double v) {
285287 return String .valueOf (Math .round (v ));
286288 }
287289
288-
289- static class Line {
290-
290+ static class TableRow {
291291 private final String tree ;
292- private final Map <String , Justified > details ;
292+ private final Map <String ,Cell > cells ;
293293 private final Optional <String > connection ;
294+ private final int height ;
294295
295- Line (String tree , Map <String , Justified > details , Optional <String > connection ) {
296+ TableRow (String tree , Map <String ,Cell > cells , Optional <String > connection ) {
296297 this .tree = tree ;
297- this .details = details ;
298+ this .cells = cells ;
298299 this .connection = connection == null ? Optional .empty () : connection ;
300+ this .height = cells .values ().stream ().mapToInt (v -> v .lines .length ).max ().orElse (0 );
299301 }
300302
301- Justified get (String key ) {
302- if (key .equals (TablePlanFormatter .OPERATOR )) {
303- return new Left (tree );
304- } else
305- return details .getOrDefault (key , new Left ("" ));
303+ Cell get (String key ) {
304+ if (key .equals (TablePlanFormatter .OPERATOR ))
305+ return new LeftJustifiedCell (tree );
306+ else
307+ return cells .getOrDefault (key , new LeftJustifiedCell ("" ));
306308 }
307309 }
308310
309- static abstract class Justified {
311+ static abstract class Cell {
310312 final int length ;
311- final String text ;
313+ final String [] lines ;
314+
315+ Cell (String [] lines ) {
316+ this .length = Stream .of (lines ).mapToInt (String ::length ).max ().orElse (0 );
317+ this .lines = lines ;
318+ }
319+
320+ abstract void writePaddedLine (int lineIndex , String orElseValue , int columnWidth , StringBuilder result );
321+
322+ protected int paddingWidth (int columnWidth , String line ) {
323+ return columnWidth - line .length () - 2 ;
324+ }
312325
313- Justified (String text ) {
314- this .length = text .length ();
315- this .text = text ;
326+ protected String getLineOrElse (int lineIndex , String orElseValue ) {
327+ if (lineIndex < lines .length )
328+ return lines [lineIndex ];
329+ else
330+ return orElseValue ;
316331 }
317332 }
318333
319- static class Left extends Justified {
320- Left (String text ) {
321- super (text );
334+ static class LeftJustifiedCell extends Cell
335+ {
336+ LeftJustifiedCell (String ... lines ) {
337+ super (lines );
338+ }
339+
340+ @ Override
341+ void writePaddedLine (int lineIndex , String orElseValue , int columnWidth , StringBuilder result ) {
342+ String line = getLineOrElse (lineIndex , orElseValue );
343+ result .append (line );
344+ pad (paddingWidth (columnWidth , line ), ' ' , result );
322345 }
323346 }
324347
325- static class Right extends Justified {
326- Right (String text ) {
327- super (text );
348+ static class RightJustifiedCell extends Cell {
349+ RightJustifiedCell (String ... lines ) {
350+ super (lines );
351+ }
352+
353+ @ Override
354+ void writePaddedLine (int lineIndex , String orElseValue , int columnWidth , StringBuilder result )
355+ {
356+ String line = getLineOrElse (lineIndex , orElseValue );
357+ pad (paddingWidth (columnWidth , line ), ' ' , result );
358+ result .append (line );
328359 }
329360 }
330361
@@ -447,11 +478,16 @@ public static <T1, T2> Pair<T1, T2> of(T1 _1, T2 _2) {
447478 }
448479 }
449480
450- private String truncate ( String original ) {
451- if (original .length () <= MAX_DETAILS_COLUMN_WIDTH ){
452- return original ;
481+ private String [] splitDetails (String original ) {
482+ List <String > detailsList = new ArrayList <>();
483+
484+ int currentPos = 0 ;
485+ while (currentPos < original .length ()){
486+ int newPos = Math .min (original .length (), currentPos + MAX_DETAILS_COLUMN_WIDTH );
487+ detailsList .add (original .substring ( currentPos , newPos ));
488+ currentPos = newPos ;
453489 }
454490
455- return original . substring ( 0 , MAX_DETAILS_COLUMN_WIDTH - 3 ) + "..." ;
491+ return detailsList . toArray ( new String [ 0 ]) ;
456492 }
457493}
0 commit comments