1414import java .util .ArrayList ;
1515import java .util .Arrays ;
1616import java .util .Collections ;
17+ import java .util .Comparator ;
1718import java .util .LinkedHashMap ;
1819import java .util .List ;
1920import java .util .Map ;
@@ -169,7 +170,8 @@ public void setType(String type) {
169170 }
170171
171172 public List <GenericPlanNode > getPlans () {
172- return plans .isEmpty () ? null : plans ; // for simplified json
173+ List <GenericPlanNode > orderedPlans = orderedPlansForDisplay ();
174+ return orderedPlans .isEmpty () ? null : orderedPlans ; // for simplified json
173175 }
174176
175177 public void setPlans (List <GenericPlanNode > plans ) {
@@ -575,16 +577,18 @@ public void setAlgorithm(String algorithm) {
575577 */
576578 @ Override
577579 public String toString () {
578- return getHumanReadable (0 );
580+ return getHumanReadable (0 , isProjectionElemListNode () );
579581 }
580582
581583 /**
582584 * @param prettyBoxDrawingType for deciding if we should use single or double walled character for drawing the
583585 * connectors between nodes in the query plan. Eg. ├ or ╠ and ─ o
586+ * @param ordered
584587 * @return
585588 */
586- private String getHumanReadable (int prettyBoxDrawingType ) {
589+ private String getHumanReadable (int prettyBoxDrawingType , boolean ordered ) {
587590 StringBuilder sb = new StringBuilder ();
591+ List <GenericPlanNode > displayPlans = ordered ? orderedPlansForDisplay () : plans ;
588592
589593 if (timedOut != null && timedOut ) {
590594 sb .append ("Timed out while retrieving explanation! Explanation may be incomplete!" ).append (newLine );
@@ -607,7 +611,7 @@ private String getHumanReadable(int prettyBoxDrawingType) {
607611 // we use box-drawing characters to "group" nodes in the plan visually when there are exactly two child plans
608612 // and
609613 // the child plans contain child plans
610- if (plans .size () == 2 && plans .stream ().anyMatch (p -> !p .plans .isEmpty ())) {
614+ if (displayPlans .size () == 2 && displayPlans .stream ().anyMatch (p -> !p .plans .isEmpty ())) {
611615
612616 String start ;
613617 String horizontal ;
@@ -626,8 +630,8 @@ private String getHumanReadable(int prettyBoxDrawingType) {
626630 end = "└" ;
627631 }
628632
629- String left = plans .get (0 ).getHumanReadable (prettyBoxDrawingType + 1 );
630- String right = plans .get (1 ).getHumanReadable (prettyBoxDrawingType + 1 );
633+ String left = displayPlans .get (0 ).getHumanReadable (prettyBoxDrawingType + 1 , false );
634+ String right = displayPlans .get (1 ).getHumanReadable (prettyBoxDrawingType + 1 , false );
631635 boolean join = type .contains ("Join" );
632636
633637 {
@@ -655,10 +659,14 @@ private String getHumanReadable(int prettyBoxDrawingType) {
655659
656660 } else {
657661
658- for (int i = 0 ; i < plans .size (); i ++) {
659- GenericPlanNode child = plans .get (i );
662+ for (int i = 0 ; i < displayPlans .size (); i ++) {
663+ GenericPlanNode child = displayPlans .get (i );
660664 int j = i ;
661- sb .append (Arrays .stream (child .getHumanReadable (prettyBoxDrawingType + 1 ).split (newLine ))
665+ sb .append (Arrays
666+ .stream (child
667+ .getHumanReadable (prettyBoxDrawingType + 1 ,
668+ child .isProjectionElemListNode () && !isMultiProjectionNode ())
669+ .split (newLine ))
662670 .map (c -> {
663671 if (type .startsWith ("StatementPattern" ) && child .type .startsWith ("Var" )) {
664672 return spoc [j ] + ": " + c ;
@@ -674,6 +682,23 @@ private String getHumanReadable(int prettyBoxDrawingType) {
674682 return sb .toString ();
675683 }
676684
685+ private List <GenericPlanNode > orderedPlansForDisplay () {
686+ if (!isProjectionElemListNode () || plans .size () < 2 ) {
687+ return plans ;
688+ }
689+ List <GenericPlanNode > orderedPlans = new ArrayList <>(plans );
690+ orderedPlans .sort (Comparator .comparing (plan -> plan .getType () == null ? "" : plan .getType ()));
691+ return orderedPlans ;
692+ }
693+
694+ private boolean isProjectionElemListNode () {
695+ return type != null && type .startsWith ("ProjectionElemList" );
696+ }
697+
698+ private boolean isMultiProjectionNode () {
699+ return type != null && type .startsWith ("MultiProjection" );
700+ }
701+
677702 /**
678703 * @return Human readable number. Eg. 12.1M for 1212213.4 and UNKNOWN for -1.
679704 */
@@ -1336,6 +1361,7 @@ private static double getMaxResultSizeActual(GenericPlanNode genericPlanNode) {
13361361 private String toDotInternal (double maxResultSizeActual , double maxTotalTime , double maxSelfTime ) {
13371362 StringBuilder sb = new StringBuilder ();
13381363 sb .append (" " );
1364+ List <GenericPlanNode > displayPlans = orderedPlansForDisplay ();
13391365
13401366 if (newScope != null && newScope ) {
13411367 sb .append ("subgraph cluster_" )
@@ -1377,13 +1403,13 @@ private String toDotInternal(double maxResultSizeActual, double maxTotalTime, do
13771403 .orElse ("" ));
13781404
13791405 sb .append ("</table>>" ).append (" shape=plaintext];" ).append (newLine );
1380- for (int i = 0 ; i < plans .size (); i ++) {
1381- GenericPlanNode p = plans .get (i );
1406+ for (int i = 0 ; i < displayPlans .size (); i ++) {
1407+ GenericPlanNode p = displayPlans .get (i );
13821408 String linkLabel = "index " + i ;
13831409
1384- if (plans .size () == 2 ) {
1410+ if (displayPlans .size () == 2 ) {
13851411 linkLabel = i == 0 ? "left" : "right" ;
1386- } else if (plans .size () == 1 ) {
1412+ } else if (displayPlans .size () == 1 ) {
13871413 linkLabel = "" ;
13881414 }
13891415
@@ -1398,7 +1424,7 @@ private String toDotInternal(double maxResultSizeActual, double maxTotalTime, do
13981424 .append (newLine );
13991425 }
14001426
1401- plans .forEach (p -> sb .append (p .toDotInternal (maxResultSizeActual , maxTotalTime , maxSelfTime )));
1427+ displayPlans .forEach (p -> sb .append (p .toDotInternal (maxResultSizeActual , maxTotalTime , maxSelfTime )));
14021428
14031429 if (newScope != null && newScope ) {
14041430 sb .append (newLine ).append ("}" ).append (newLine );
0 commit comments