You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by mo...@apache.org on 2020/03/02 11:34:52 UTC

[incubator-doris] branch master updated: [SetOperation] Change set operation from random shuffle to hash shuffle (#3015)

This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 54aa0ed  [SetOperation] Change set operation from random shuffle to hash shuffle (#3015)
54aa0ed is described below

commit 54aa0ed26b909518f907e762fbebc6e836e9b440
Author: yangzhg <78...@qq.com>
AuthorDate: Mon Mar 2 19:34:41 2020 +0800

    [SetOperation] Change set operation from random shuffle to hash shuffle (#3015)
    
    use hash shuffle instead of random shuffle in set operation, prepare for intersect and except operation
---
 .../apache/doris/analysis/SetOperationStmt.java    |    4 +-
 .../apache/doris/planner/DistributedPlanner.java   |    3 +-
 .../org/apache/doris/planner/HashJoinNode.java     |    2 +-
 .../org/apache/doris/planner/SetOperationNode.java |   10 +-
 .../apache/doris/planner/SingleNodePlanner.java    |   48 +-
 .../java/org/apache/doris/planner/PlannerTest.java | 1890 +-------------------
 6 files changed, 73 insertions(+), 1884 deletions(-)

diff --git a/fe/src/main/java/org/apache/doris/analysis/SetOperationStmt.java b/fe/src/main/java/org/apache/doris/analysis/SetOperationStmt.java
index 29def10..1a24fca 100644
--- a/fe/src/main/java/org/apache/doris/analysis/SetOperationStmt.java
+++ b/fe/src/main/java/org/apache/doris/analysis/SetOperationStmt.java
@@ -649,7 +649,9 @@ public class SetOperationStmt extends QueryStmt {
             if (isAnalyzed()) {
                 return;
             }
-            if (qualifier_ == Qualifier.ALL && operation != Operation.UNION) {
+            // Oracle and ms-SQLServer do not support INTERSECT ALL and EXCEPT ALL, postgres support it,
+            // but it is very ambiguous
+            if (qualifier_ == Qualifier.ALL && (operation == Operation.EXCEPT || operation == Operation.INTERSECT)) {
                 throw new AnalysisException("INTERSECT and EXCEPT does not support ALL qualifier.");
             }
             analyzer = new Analyzer(parent);
diff --git a/fe/src/main/java/org/apache/doris/planner/DistributedPlanner.java b/fe/src/main/java/org/apache/doris/planner/DistributedPlanner.java
index c3adbe1..9c3bce3 100644
--- a/fe/src/main/java/org/apache/doris/planner/DistributedPlanner.java
+++ b/fe/src/main/java/org/apache/doris/planner/DistributedPlanner.java
@@ -671,7 +671,8 @@ public class DistributedPlanner {
             setOperationNode.addChild(null);
             // Connect the unpartitioned child fragments to SetOperationNode via a random exchange.
             connectChildFragment(setOperationNode, i, setOperationFragment, childFragment);
-            childFragment.setOutputPartition(DataPartition.RANDOM);
+            childFragment.setOutputPartition(
+                    DataPartition.hashPartitioned(setOperationNode.getMaterializedResultExprLists_().get(i)));
         }
         setOperationNode.init(ctx_.getRootAnalyzer());
         return setOperationFragment;
diff --git a/fe/src/main/java/org/apache/doris/planner/HashJoinNode.java b/fe/src/main/java/org/apache/doris/planner/HashJoinNode.java
index 64bd9d4..04a69c7 100644
--- a/fe/src/main/java/org/apache/doris/planner/HashJoinNode.java
+++ b/fe/src/main/java/org/apache/doris/planner/HashJoinNode.java
@@ -277,7 +277,7 @@ public class HashJoinNode extends PlanNode {
         output.append(detailPrefix + "colocate: " + isColocate + (isColocate? "" : ", reason: " + colocateReason) + "\n");
 
         for (BinaryPredicate eqJoinPredicate : eqJoinConjuncts) {
-            output.append(eqJoinPredicate.toSql() +  "\n");
+            output.append(detailPrefix).append("equal join conjunct: ").append(eqJoinPredicate.toSql() +  "\n");
         }
         if (!otherJoinConjuncts.isEmpty()) {
             output.append(detailPrefix + "other join predicates: ").append(
diff --git a/fe/src/main/java/org/apache/doris/planner/SetOperationNode.java b/fe/src/main/java/org/apache/doris/planner/SetOperationNode.java
index a6623cd..cb92707 100644
--- a/fe/src/main/java/org/apache/doris/planner/SetOperationNode.java
+++ b/fe/src/main/java/org/apache/doris/planner/SetOperationNode.java
@@ -54,7 +54,7 @@ import com.google.common.collect.Lists;
 public abstract class SetOperationNode extends PlanNode {
     private final static Logger LOG = LoggerFactory.getLogger(SetOperationNode.class);
 
-    // List of set operation result exprs of the originating UnionStmt. Used for
+    // List of set operation result exprs of the originating SetOperationStmt. Used for
     // determining passthrough-compatibility of children.
     protected List<Expr> setOpResultExprs_;
 
@@ -114,6 +114,14 @@ public abstract class SetOperationNode extends PlanNode {
         resultExprLists_.add(resultExprs);
     }
 
+    public List<List<Expr>> getMaterializedResultExprLists_() {
+        return materializedResultExprLists_;
+    }
+
+    public List<List<Expr>> getMaterializedConstExprLists_() {
+        return materializedConstExprLists_;
+    }
+
     @Override
     public void computeStats(Analyzer analyzer) {
         super.computeStats(analyzer);
diff --git a/fe/src/main/java/org/apache/doris/planner/SingleNodePlanner.java b/fe/src/main/java/org/apache/doris/planner/SingleNodePlanner.java
index ca88171..1ebe7bb 100644
--- a/fe/src/main/java/org/apache/doris/planner/SingleNodePlanner.java
+++ b/fe/src/main/java/org/apache/doris/planner/SingleNodePlanner.java
@@ -1584,6 +1584,11 @@ public class SingleNodePlanner {
         // If it is a union or other same operation, there are only two possibilities,
         // one is the root node, and the other is a distinct node in front, so the setOperationDistinctPlan will
         // be aggregate node, if this is a mixed operation
+        // e.g. :
+        // a union b -> setOperationDistinctPlan == null
+        // a union b union all c -> setOperationDistinctPlan == null -> setOperationDistinctPlan == AggregationNode
+        // a union all b except c -> setOperationDistinctPlan == null -> setOperationDistinctPlan == UnionNode
+        // a union b except c -> setOperationDistinctPlan == null -> setOperationDistinctPlan == AggregationNode
         if (setOperationDistinctPlan != null && setOperationDistinctPlan instanceof SetOperationNode) {
             Preconditions.checkState(!setOperationDistinctPlan.getClass().equals(setOpNode.getClass()));
             setOpNode.addChild(setOperationDistinctPlan, setOperationStmt.getResultExprs());
@@ -1591,7 +1596,8 @@ public class SingleNodePlanner {
             Preconditions.checkState(setOperationStmt.hasDistinctOps());
             Preconditions.checkState(setOperationDistinctPlan instanceof AggregationNode);
             setOpNode.addChild(setOperationDistinctPlan,
-                    setOperationStmt.getDistinctAggInfo().getGroupingExprs());        }
+                    setOperationStmt.getDistinctAggInfo().getGroupingExprs());
+        }
         setOpNode.init(analyzer);
         return setOpNode;
     }
@@ -1701,9 +1707,11 @@ public class SingleNodePlanner {
         return result;
     }
 
-    // create partial plan  for example: a union b intersect c
-    // first partial plan is a union b as a result d,
+    // create the partial plan, or example: a union b intersect c
+    // the first partial plan is a union b as a result d,
     // the second partial plan d intersect c
+    // notice that when query is a union b the union operation is in right-hand child(b),
+    // while the left-hand child(a)'s operation is null
     private PlanNode createPartialSetOperationPlan(Analyzer analyzer, SetOperationStmt setOperationStmt,
                                                    List<SetOperationStmt.SetOperand> setOperands,
                                                    PlanNode setOperationDistinctPlan, long defaultOrderByLimit)
@@ -1712,6 +1720,7 @@ public class SingleNodePlanner {
         boolean hasAllOps = false;
         List<SetOperationStmt.SetOperand> allOps = new ArrayList<>();
         List<SetOperationStmt.SetOperand> distinctOps = new ArrayList<>();
+        SetOperationStmt.Operation operation = null;
         for (SetOperationStmt.SetOperand op: setOperands) {
             if (op.getQualifier() == SetOperationStmt.Qualifier.DISTINCT) {
                 hasDistinctOps = true;
@@ -1721,19 +1730,30 @@ public class SingleNodePlanner {
                 hasAllOps = true;
                 allOps.add(op);
             }
+            if (operation == null || operation == op.getOperation()) {
+                operation = op.getOperation();
+            } else {
+                operation = null;
+            }
         }
-        // create DISTINCT tree
-        if (hasDistinctOps) {
+        Preconditions.checkNotNull(operation, "invalid operation.");
+        if (operation == SetOperationStmt.Operation.INTERSECT || operation == SetOperationStmt.Operation.EXCEPT) {
             setOperationDistinctPlan = createSetOperationPlan(
-                    analyzer, setOperationStmt, distinctOps, setOperationDistinctPlan, defaultOrderByLimit);
-            setOperationDistinctPlan = new AggregationNode(ctx_.getNextNodeId(), setOperationDistinctPlan,
-                    setOperationStmt.getDistinctAggInfo());
-            setOperationDistinctPlan.init(analyzer);
-        }
-        // create ALL tree
-        if (hasAllOps) {
-            setOperationDistinctPlan = createSetOperationPlan(analyzer, setOperationStmt, allOps,
-                    setOperationDistinctPlan, defaultOrderByLimit);
+                    analyzer, setOperationStmt, setOperands, setOperationDistinctPlan, defaultOrderByLimit);
+        } else {
+            // create DISTINCT tree
+            if (hasDistinctOps) {
+                setOperationDistinctPlan = createSetOperationPlan(
+                        analyzer, setOperationStmt, distinctOps, setOperationDistinctPlan, defaultOrderByLimit);
+                setOperationDistinctPlan = new AggregationNode(ctx_.getNextNodeId(), setOperationDistinctPlan,
+                        setOperationStmt.getDistinctAggInfo());
+                setOperationDistinctPlan.init(analyzer);
+            }
+            // create ALL tree
+            if (hasAllOps) {
+                setOperationDistinctPlan = createSetOperationPlan(analyzer, setOperationStmt, allOps,
+                        setOperationDistinctPlan, defaultOrderByLimit);
+            }
         }
         return setOperationDistinctPlan;
     }
diff --git a/fe/src/test/java/org/apache/doris/planner/PlannerTest.java b/fe/src/test/java/org/apache/doris/planner/PlannerTest.java
index 663133c..56a2e6f 100644
--- a/fe/src/test/java/org/apache/doris/planner/PlannerTest.java
+++ b/fe/src/test/java/org/apache/doris/planner/PlannerTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.planner;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.doris.analysis.CreateDbStmt;
 import org.apache.doris.analysis.CreateTableStmt;
 import org.apache.doris.catalog.Catalog;
@@ -64,100 +65,12 @@ public class PlannerTest {
                 + "  db1.tbl1 b\n"
                 + "  on (a.k1 = b.k1)\n"
                 + "where b.k1 = 'a'";
-        String plan1 = "PLAN FRAGMENT 0\n" +
-                " OUTPUT EXPRS:<slot 4> | <slot 5> | `b`.`k1` | `b`.`k2` | `b`.`k3` | `b`.`k4`\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  RESULT SINK\n" +
-                "\n" +
-                "  4:HASH JOIN\n" +
-                "  |  join op: INNER JOIN (BROADCAST)\n" +
-                "  |  hash predicates:\n" +
-                "  |  colocate: false, reason: Node type not match\n" +
-                "(<slot 4> = `b`.`k1`)\n" +
-                "  |  tuple ids: 2 4 \n" +
-                "  |  \n" +
-                "  |----7:EXCHANGE\n" +
-                "  |       tuple ids: 4 \n" +
-                "  |    \n" +
-                "  0:UNION\n" +
-                "  |  child exprs: \n" +
-                "  |      `k1` | `k2`\n" +
-                "  |      `k1` | `k2`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 2 \n" +
-                "  |  \n" +
-                "  |----6:EXCHANGE\n" +
-                "  |       tuple ids: 1 \n" +
-                "  |    \n" +
-                "  5:EXCHANGE\n" +
-                "     tuple ids: 0 \n" +
-                "\n" +
-                "PLAN FRAGMENT 1\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 07\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  3:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: null\n" +
-                "     PREDICATES: `b`.`k1` = 'a'\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 2\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 06\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  2:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 1 \n" +
-                "\n" +
-                "PLAN FRAGMENT 3\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 05\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  1:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 0 \n";
         StmtExecutor stmtExecutor1 = new StmtExecutor(ctx, sql1);
         stmtExecutor1.execute();
         Planner planner1 = stmtExecutor1.planner();
         List<PlanFragment> fragments1 = planner1.getFragments();
-        Assert.assertEquals(plan1, planner1.getExplainString(fragments1, TExplainLevel.VERBOSE));
+        String plan1 = planner1.getExplainString(fragments1, TExplainLevel.VERBOSE);
+        Assert.assertEquals(1, StringUtils.countMatches(plan1, "UNION"));
         String sql2 = "explain select * from db1.tbl1 where k1='a' and k4=1\n"
                 + "union distinct\n"
                 + "  (select * from db1.tbl1 where k1='b' and k4=2\n"
@@ -177,351 +90,13 @@ public class PlannerTest {
                 + "   union all\n"
                 + "   (select * from db1.tbl1 where k1='b' and k4=5)\n"
                 + "   order by 3 limit 3)";
-
-        String plan2 = "PLAN FRAGMENT 0\n" +
-                " OUTPUT EXPRS:<slot 60> | <slot 61> | <slot 62> | <slot 63>\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  RESULT SINK\n" +
-                "\n" +
-                "  30:EXCHANGE\n" +
-                "     tuple ids: 15 \n" +
-                "\n" +
-                "PLAN FRAGMENT 1\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 30\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  9:UNION\n" +
-                "  |  child exprs: \n" +
-                "  |      <slot 60> | <slot 61> | <slot 62> | <slot 63>\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      <slot 57> | <slot 58> | <slot 56> | <slot 59>\n" +
-                "  |  pass-through-operands: 26,27,28\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  |----27:EXCHANGE\n" +
-                "  |       tuple ids: 8 \n" +
-                "  |    \n" +
-                "  |----28:EXCHANGE\n" +
-                "  |       tuple ids: 9 \n" +
-                "  |    \n" +
-                "  |----29:EXCHANGE\n" +
-                "  |       limit: 3\n" +
-                "  |       tuple ids: 14 \n" +
-                "  |    \n" +
-                "  26:EXCHANGE\n" +
-                "     tuple ids: 15 \n" +
-                "\n" +
-                "PLAN FRAGMENT 2\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 29\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  25:MERGING-EXCHANGE\n" +
-                "     limit: 3\n" +
-                "     tuple ids: 14 \n" +
-                "\n" +
-                "PLAN FRAGMENT 3\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 25\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  15:TOP-N\n" +
-                "  |  order by: <slot 56> ASC\n" +
-                "  |  offset: 0\n" +
-                "  |  limit: 3\n" +
-                "  |  tuple ids: 14 \n" +
-                "  |  \n" +
-                "  12:UNION\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 13 \n" +
-                "  |  \n" +
-                "  |----24:EXCHANGE\n" +
-                "  |       tuple ids: 12 \n" +
-                "  |    \n" +
-                "  23:EXCHANGE\n" +
-                "     tuple ids: 11 \n" +
-                "\n" +
-                "PLAN FRAGMENT 4\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 24\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  14:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 5\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 12 \n" +
-                "\n" +
-                "PLAN FRAGMENT 5\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 23\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  13:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 3\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 11 \n" +
-                "\n" +
-                "PLAN FRAGMENT 6\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 28\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  11:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 4\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 9 \n" +
-                "\n" +
-                "PLAN FRAGMENT 7\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 27\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  10:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 3\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 8 \n" +
-                "\n" +
-                "PLAN FRAGMENT 8\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 26\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  8:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  0:UNION\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      <slot 29> | <slot 30> | <slot 28> | <slot 31>\n" +
-                "  |  pass-through-operands: 19,20,21\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  |----20:EXCHANGE\n" +
-                "  |       tuple ids: 1 \n" +
-                "  |    \n" +
-                "  |----21:EXCHANGE\n" +
-                "  |       tuple ids: 2 \n" +
-                "  |    \n" +
-                "  |----22:EXCHANGE\n" +
-                "  |       limit: 3\n" +
-                "  |       tuple ids: 7 \n" +
-                "  |    \n" +
-                "  19:EXCHANGE\n" +
-                "     tuple ids: 0 \n" +
-                "\n" +
-                "PLAN FRAGMENT 9\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 22\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  18:MERGING-EXCHANGE\n" +
-                "     limit: 3\n" +
-                "     tuple ids: 7 \n" +
-                "\n" +
-                "PLAN FRAGMENT 10\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 18\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  7:TOP-N\n" +
-                "  |  order by: <slot 28> ASC\n" +
-                "  |  offset: 0\n" +
-                "  |  limit: 3\n" +
-                "  |  tuple ids: 7 \n" +
-                "  |  \n" +
-                "  4:UNION\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 6 \n" +
-                "  |  \n" +
-                "  |----17:EXCHANGE\n" +
-                "  |       tuple ids: 5 \n" +
-                "  |    \n" +
-                "  16:EXCHANGE\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 11\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 17\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  6:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 3\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 5 \n" +
-                "\n" +
-                "PLAN FRAGMENT 12\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 16\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  5:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 13\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 21\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  3:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 2 \n" +
-                "\n" +
-                "PLAN FRAGMENT 14\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 20\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  2:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 1 \n" +
-                "\n" +
-                "PLAN FRAGMENT 15\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 19\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  1:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 1\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 0 \n";
         StmtExecutor stmtExecutor2 = new StmtExecutor(ctx, sql2);
         stmtExecutor2.execute();
         Planner planner2 = stmtExecutor2.planner();
         List<PlanFragment> fragments2 = planner2.getFragments();
-        Assert.assertEquals(plan2, planner2.getExplainString(fragments2, TExplainLevel.VERBOSE));
+        String plan2 = planner2.getExplainString(fragments2, TExplainLevel.VERBOSE);
+        Assert.assertEquals(4, StringUtils.countMatches(plan2, "UNION"));
+
         // intersect
         String sql3 = "explain select * from\n"
                 + "  (select k1, k2 from db1.tbl1\n"
@@ -531,104 +106,12 @@ public class PlannerTest {
                 + "  db1.tbl1 b\n"
                 + "  on (a.k1 = b.k1)\n"
                 + "where b.k1 = 'a'";
-        String plan3 = "PLAN FRAGMENT 0\n" +
-                " OUTPUT EXPRS:<slot 4> | <slot 5> | `b`.`k1` | `b`.`k2` | `b`.`k3` | `b`.`k4`\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  RESULT SINK\n" +
-                "\n" +
-                "  5:HASH JOIN\n" +
-                "  |  join op: INNER JOIN (BROADCAST)\n" +
-                "  |  hash predicates:\n" +
-                "  |  colocate: false, reason: Node type not match\n" +
-                "(<slot 4> = `b`.`k1`)\n" +
-                "  |  tuple ids: 2 4 \n" +
-                "  |  \n" +
-                "  |----8:EXCHANGE\n" +
-                "  |       tuple ids: 4 \n" +
-                "  |    \n" +
-                "  3:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 4>, <slot 5>\n" +
-                "  |  tuple ids: 2 \n" +
-                "  |  \n" +
-                "  0:INTERSECT\n" +
-                "  |  child exprs: \n" +
-                "  |      `k1` | `k2`\n" +
-                "  |      `k1` | `k2`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 2 \n" +
-                "  |  \n" +
-                "  |----7:EXCHANGE\n" +
-                "  |       tuple ids: 1 \n" +
-                "  |    \n" +
-                "  6:EXCHANGE\n" +
-                "     tuple ids: 0 \n" +
-                "\n" +
-                "PLAN FRAGMENT 1\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 08\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  4:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: null\n" +
-                "     PREDICATES: `b`.`k1` = 'a'\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 2\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 07\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  2:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 1 \n" +
-                "\n" +
-                "PLAN FRAGMENT 3\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 06\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  1:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 0 \n";
         StmtExecutor stmtExecutor3 = new StmtExecutor(ctx, sql3);
         stmtExecutor3.execute();
         Planner planner3 = stmtExecutor3.planner();
         List<PlanFragment> fragments3 = planner3.getFragments();
-        Assert.assertEquals(plan3, planner3.getExplainString(fragments3, TExplainLevel.VERBOSE));
+        String plan3 = planner3.getExplainString(fragments3, TExplainLevel.VERBOSE);
+        Assert.assertEquals(1, StringUtils.countMatches(plan3, "INTERSECT"));
         String sql4 = "explain select * from db1.tbl1 where k1='a' and k4=1\n"
                 + "intersect distinct\n"
                 + "  (select * from db1.tbl1 where k1='b' and k4=2\n"
@@ -649,357 +132,12 @@ public class PlannerTest {
                 + "   (select * from db1.tbl1 where k1='b' and k4=5)\n"
                 + "   order by 3 limit 3)";
 
-        String plan4 = "PLAN FRAGMENT 0\n" +
-                " OUTPUT EXPRS:<slot 60> | <slot 61> | <slot 62> | <slot 63>\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  RESULT SINK\n" +
-                "\n" +
-                "  32:EXCHANGE\n" +
-                "     tuple ids: 15 \n" +
-                "\n" +
-                "PLAN FRAGMENT 1\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: HASH_PARTITIONED: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 32\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  31:AGGREGATE (merge finalize)\n" +
-                "  |  group by: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  30:EXCHANGE\n" +
-                "     tuple ids: 15 \n" +
-                "\n" +
-                "PLAN FRAGMENT 2\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 30\n" +
-                "    HASH_PARTITIONED: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "\n" +
-                "  16:AGGREGATE (update serialize)\n" +
-                "  |  STREAMING\n" +
-                "  |  group by: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  0:INTERSECT\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      <slot 29> | <slot 30> | <slot 28> | <slot 31>\n" +
-                "  |      <slot 57> | <slot 58> | <slot 56> | <slot 59>\n" +
-                "  |  pass-through-operands: 23,24,25,27,28\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  |----24:EXCHANGE\n" +
-                "  |       tuple ids: 1 \n" +
-                "  |    \n" +
-                "  |----25:EXCHANGE\n" +
-                "  |       tuple ids: 2 \n" +
-                "  |    \n" +
-                "  |----27:EXCHANGE\n" +
-                "  |       tuple ids: 8 \n" +
-                "  |    \n" +
-                "  |----28:EXCHANGE\n" +
-                "  |       tuple ids: 9 \n" +
-                "  |    \n" +
-                "  |----26:EXCHANGE\n" +
-                "  |       limit: 3\n" +
-                "  |       tuple ids: 7 \n" +
-                "  |    \n" +
-                "  |----29:EXCHANGE\n" +
-                "  |       limit: 3\n" +
-                "  |       tuple ids: 14 \n" +
-                "  |    \n" +
-                "  23:EXCHANGE\n" +
-                "     tuple ids: 0 \n" +
-                "\n" +
-                "PLAN FRAGMENT 3\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 29\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  22:MERGING-EXCHANGE\n" +
-                "     limit: 3\n" +
-                "     tuple ids: 14 \n" +
-                "\n" +
-                "PLAN FRAGMENT 4\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 22\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  15:TOP-N\n" +
-                "  |  order by: <slot 56> ASC\n" +
-                "  |  offset: 0\n" +
-                "  |  limit: 3\n" +
-                "  |  tuple ids: 14 \n" +
-                "  |  \n" +
-                "  14:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 52>, <slot 53>, <slot 54>, <slot 55>\n" +
-                "  |  tuple ids: 13 \n" +
-                "  |  \n" +
-                "  11:INTERSECT\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 13 \n" +
-                "  |  \n" +
-                "  |----21:EXCHANGE\n" +
-                "  |       tuple ids: 12 \n" +
-                "  |    \n" +
-                "  20:EXCHANGE\n" +
-                "     tuple ids: 11 \n" +
-                "\n" +
-                "PLAN FRAGMENT 5\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 21\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  13:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 5\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 12 \n" +
-                "\n" +
-                "PLAN FRAGMENT 6\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 20\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  12:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 3\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 11 \n" +
-                "\n" +
-                "PLAN FRAGMENT 7\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 28\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  10:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 4\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 9 \n" +
-                "\n" +
-                "PLAN FRAGMENT 8\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 27\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  9:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 3\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 8 \n" +
-                "\n" +
-                "PLAN FRAGMENT 9\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 26\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  19:MERGING-EXCHANGE\n" +
-                "     limit: 3\n" +
-                "     tuple ids: 7 \n" +
-                "\n" +
-                "PLAN FRAGMENT 10\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 19\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  8:TOP-N\n" +
-                "  |  order by: <slot 28> ASC\n" +
-                "  |  offset: 0\n" +
-                "  |  limit: 3\n" +
-                "  |  tuple ids: 7 \n" +
-                "  |  \n" +
-                "  7:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 24>, <slot 25>, <slot 26>, <slot 27>\n" +
-                "  |  tuple ids: 6 \n" +
-                "  |  \n" +
-                "  4:INTERSECT\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 6 \n" +
-                "  |  \n" +
-                "  |----18:EXCHANGE\n" +
-                "  |       tuple ids: 5 \n" +
-                "  |    \n" +
-                "  17:EXCHANGE\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 11\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 18\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  6:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 3\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 5 \n" +
-                "\n" +
-                "PLAN FRAGMENT 12\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 17\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  5:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 13\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 25\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  3:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 2 \n" +
-                "\n" +
-                "PLAN FRAGMENT 14\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 24\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  2:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 1 \n" +
-                "\n" +
-                "PLAN FRAGMENT 15\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 23\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  1:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 1\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 0 \n";
         StmtExecutor stmtExecutor4 = new StmtExecutor(ctx, sql4);
         stmtExecutor4.execute();
         Planner planner4 = stmtExecutor4.planner();
         List<PlanFragment> fragments4 = planner4.getFragments();
-        Assert.assertEquals(plan4, planner4.getExplainString(fragments4, TExplainLevel.VERBOSE));
+        String plan4 = planner4.getExplainString(fragments4, TExplainLevel.VERBOSE);
+        Assert.assertEquals(3, StringUtils.countMatches(plan4, "INTERSECT"));
 
         // except
         String sql5 = "explain select * from\n"
@@ -1010,104 +148,12 @@ public class PlannerTest {
                 + "  db1.tbl1 b\n"
                 + "  on (a.k1 = b.k1)\n"
                 + "where b.k1 = 'a'";
-        String plan5 = "PLAN FRAGMENT 0\n" +
-                " OUTPUT EXPRS:<slot 4> | <slot 5> | `b`.`k1` | `b`.`k2` | `b`.`k3` | `b`.`k4`\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  RESULT SINK\n" +
-                "\n" +
-                "  5:HASH JOIN\n" +
-                "  |  join op: INNER JOIN (BROADCAST)\n" +
-                "  |  hash predicates:\n" +
-                "  |  colocate: false, reason: Node type not match\n" +
-                "(<slot 4> = `b`.`k1`)\n" +
-                "  |  tuple ids: 2 4 \n" +
-                "  |  \n" +
-                "  |----8:EXCHANGE\n" +
-                "  |       tuple ids: 4 \n" +
-                "  |    \n" +
-                "  3:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 4>, <slot 5>\n" +
-                "  |  tuple ids: 2 \n" +
-                "  |  \n" +
-                "  0:EXCEPT\n" +
-                "  |  child exprs: \n" +
-                "  |      `k1` | `k2`\n" +
-                "  |      `k1` | `k2`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 2 \n" +
-                "  |  \n" +
-                "  |----7:EXCHANGE\n" +
-                "  |       tuple ids: 1 \n" +
-                "  |    \n" +
-                "  6:EXCHANGE\n" +
-                "     tuple ids: 0 \n" +
-                "\n" +
-                "PLAN FRAGMENT 1\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 08\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  4:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: null\n" +
-                "     PREDICATES: `b`.`k1` = 'a'\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 2\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 07\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  2:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 1 \n" +
-                "\n" +
-                "PLAN FRAGMENT 3\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 06\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  1:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 0 \n";
         StmtExecutor stmtExecutor5 = new StmtExecutor(ctx, sql5);
         stmtExecutor5.execute();
         Planner planner5 = stmtExecutor5.planner();
         List<PlanFragment> fragments5 = planner5.getFragments();
-        Assert.assertEquals(plan5, planner5.getExplainString(fragments5, TExplainLevel.VERBOSE));
+        String plan5 = planner5.getExplainString(fragments5, TExplainLevel.VERBOSE);
+        Assert.assertEquals(1, StringUtils.countMatches(plan5, "EXCEPT"));
 
         String sql6 = "select * from db1.tbl1 where k1='a' and k4=1\n"
                 + "except\n"
@@ -1117,143 +163,12 @@ public class PlannerTest {
                 + "except distinct\n"
                 + "(select * from db1.tbl1 where k1='a' and k4=2)\n"
                 + "order by 3 limit 3";
-        String plan6 = "PLAN FRAGMENT 0\n" +
-                " OUTPUT EXPRS:<slot 21> | <slot 22> | <slot 20> | <slot 23>\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  RESULT SINK\n" +
-                "\n" +
-                "  11:MERGING-EXCHANGE\n" +
-                "     limit: 3\n" +
-                "     tuple ids: 5 \n" +
-                "\n" +
-                "PLAN FRAGMENT 1\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 11\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  6:TOP-N\n" +
-                "  |  order by: <slot 20> ASC\n" +
-                "  |  offset: 0\n" +
-                "  |  limit: 3\n" +
-                "  |  tuple ids: 5 \n" +
-                "  |  \n" +
-                "  5:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 16>, <slot 17>, <slot 18>, <slot 19>\n" +
-                "  |  tuple ids: 4 \n" +
-                "  |  \n" +
-                "  0:EXCEPT\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 4 \n" +
-                "  |  \n" +
-                "  |----8:EXCHANGE\n" +
-                "  |       tuple ids: 1 \n" +
-                "  |    \n" +
-                "  |----9:EXCHANGE\n" +
-                "  |       tuple ids: 2 \n" +
-                "  |    \n" +
-                "  |----10:EXCHANGE\n" +
-                "  |       tuple ids: 3 \n" +
-                "  |    \n" +
-                "  7:EXCHANGE\n" +
-                "     tuple ids: 0 \n" +
-                "\n" +
-                "PLAN FRAGMENT 2\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 10\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  4:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 3 \n" +
-                "\n" +
-                "PLAN FRAGMENT 3\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 09\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  3:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 2 \n" +
-                "\n" +
-                "PLAN FRAGMENT 4\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 08\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  2:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 1\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 1 \n" +
-                "\n" +
-                "PLAN FRAGMENT 5\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 07\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  1:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 1\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 0 \n";
         StmtExecutor stmtExecutor6 = new StmtExecutor(ctx, sql6);
         stmtExecutor6.execute();
         Planner planner6 = stmtExecutor6.planner();
         List<PlanFragment> fragments6 = planner6.getFragments();
-        Assert.assertEquals(plan6, planner6.getExplainString(fragments6, TExplainLevel.VERBOSE));
+        String plan6 = planner6.getExplainString(fragments6, TExplainLevel.VERBOSE);
+        Assert.assertEquals(1, StringUtils.countMatches(plan6, "EXCEPT"));
 
         String sql7 = "select * from db1.tbl1 where k1='a' and k4=1\n"
                 + "except distinct\n"
@@ -1263,143 +178,12 @@ public class PlannerTest {
                 + "except\n"
                 + "(select * from db1.tbl1 where k1='a' and k4=2)\n"
                 + "order by 3 limit 3";
-        String plan7 = "PLAN FRAGMENT 0\n" +
-                " OUTPUT EXPRS:<slot 21> | <slot 22> | <slot 20> | <slot 23>\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  RESULT SINK\n" +
-                "\n" +
-                "  11:MERGING-EXCHANGE\n" +
-                "     limit: 3\n" +
-                "     tuple ids: 5 \n" +
-                "\n" +
-                "PLAN FRAGMENT 1\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 11\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  6:TOP-N\n" +
-                "  |  order by: <slot 20> ASC\n" +
-                "  |  offset: 0\n" +
-                "  |  limit: 3\n" +
-                "  |  tuple ids: 5 \n" +
-                "  |  \n" +
-                "  5:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 16>, <slot 17>, <slot 18>, <slot 19>\n" +
-                "  |  tuple ids: 4 \n" +
-                "  |  \n" +
-                "  0:EXCEPT\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 4 \n" +
-                "  |  \n" +
-                "  |----8:EXCHANGE\n" +
-                "  |       tuple ids: 1 \n" +
-                "  |    \n" +
-                "  |----9:EXCHANGE\n" +
-                "  |       tuple ids: 2 \n" +
-                "  |    \n" +
-                "  |----10:EXCHANGE\n" +
-                "  |       tuple ids: 3 \n" +
-                "  |    \n" +
-                "  7:EXCHANGE\n" +
-                "     tuple ids: 0 \n" +
-                "\n" +
-                "PLAN FRAGMENT 2\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 10\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  4:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 3 \n" +
-                "\n" +
-                "PLAN FRAGMENT 3\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 09\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  3:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 2 \n" +
-                "\n" +
-                "PLAN FRAGMENT 4\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 08\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  2:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 1\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 1 \n" +
-                "\n" +
-                "PLAN FRAGMENT 5\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 07\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  1:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 1\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 0 \n";
         StmtExecutor stmtExecutor7 = new StmtExecutor(ctx, sql7);
         stmtExecutor7.execute();
         Planner planner7 = stmtExecutor7.planner();
         List<PlanFragment> fragments7 = planner7.getFragments();
-        Assert.assertEquals(plan7, planner7.getExplainString(fragments7, TExplainLevel.VERBOSE));
+        String plan7 = planner7.getExplainString(fragments7, TExplainLevel.VERBOSE);
+        Assert.assertEquals(1, StringUtils.countMatches(plan7, "EXCEPT"));
 
         // mixed
         String sql8 = "select * from db1.tbl1 where k1='a' and k4=1\n"
@@ -1410,185 +194,14 @@ public class PlannerTest {
                 + "intersect\n"
                 + "(select * from db1.tbl1 where k1='a' and k4=2)\n"
                 + "order by 3 limit 3";
-        String plan8 = "PLAN FRAGMENT 0\n" +
-                " OUTPUT EXPRS:<slot 21> | <slot 22> | <slot 20> | <slot 23>\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  RESULT SINK\n" +
-                "\n" +
-                "  17:MERGING-EXCHANGE\n" +
-                "     limit: 3\n" +
-                "     tuple ids: 5 \n" +
-                "\n" +
-                "PLAN FRAGMENT 1\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 17\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  10:TOP-N\n" +
-                "  |  order by: <slot 20> ASC\n" +
-                "  |  offset: 0\n" +
-                "  |  limit: 3\n" +
-                "  |  tuple ids: 5 \n" +
-                "  |  \n" +
-                "  9:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 16>, <slot 17>, <slot 18>, <slot 19>\n" +
-                "  |  tuple ids: 4 \n" +
-                "  |  \n" +
-                "  7:INTERSECT\n" +
-                "  |  child exprs: \n" +
-                "  |      <slot 16> | <slot 17> | <slot 18> | <slot 19>\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 4 \n" +
-                "  |  \n" +
-                "  |----16:EXCHANGE\n" +
-                "  |       tuple ids: 3 \n" +
-                "  |    \n" +
-                "  15:EXCHANGE\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 2\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 16\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  8:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 3 \n" +
-                "\n" +
-                "PLAN FRAGMENT 3\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 15\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  6:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 16>, <slot 17>, <slot 18>, <slot 19>\n" +
-                "  |  tuple ids: 4 \n" +
-                "  |  \n" +
-                "  4:EXCEPT\n" +
-                "  |  child exprs: \n" +
-                "  |      <slot 16> | <slot 17> | <slot 18> | <slot 19>\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 4 \n" +
-                "  |  \n" +
-                "  |----14:EXCHANGE\n" +
-                "  |       tuple ids: 2 \n" +
-                "  |    \n" +
-                "  13:EXCHANGE\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 4\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 14\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  5:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 2 \n" +
-                "\n" +
-                "PLAN FRAGMENT 5\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 13\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  3:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 16>, <slot 17>, <slot 18>, <slot 19>\n" +
-                "  |  tuple ids: 4 \n" +
-                "  |  \n" +
-                "  0:UNION\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 4 \n" +
-                "  |  \n" +
-                "  |----12:EXCHANGE\n" +
-                "  |       tuple ids: 1 \n" +
-                "  |    \n" +
-                "  11:EXCHANGE\n" +
-                "     tuple ids: 0 \n" +
-                "\n" +
-                "PLAN FRAGMENT 6\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 12\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  2:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 1\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 1 \n" +
-                "\n" +
-                "PLAN FRAGMENT 7\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 11\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  1:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 1\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 0 \n";
         StmtExecutor stmtExecutor8 = new StmtExecutor(ctx, sql8);
         stmtExecutor8.execute();
         Planner planner8 = stmtExecutor8.planner();
         List<PlanFragment> fragments8 = planner8.getFragments();
-        Assert.assertEquals(plan8, planner8.getExplainString(fragments8, TExplainLevel.VERBOSE));
+        String plan8 = planner8.getExplainString(fragments8, TExplainLevel.VERBOSE);
+        Assert.assertEquals(1, StringUtils.countMatches(plan8, "UNION"));
+        Assert.assertEquals(1, StringUtils.countMatches(plan8, "INTERSECT"));
+        Assert.assertEquals(1, StringUtils.countMatches(plan8, "EXCEPT"));
 
         String sql9 = "explain select * from db1.tbl1 where k1='a' and k4=1\n"
                 + "intersect distinct\n"
@@ -1610,469 +223,14 @@ public class PlannerTest {
                 + "   (select * from db1.tbl1 where k1='b' and k4=5)\n"
                 + "   order by 3 limit 3)";
 
-        String plan9 = "PLAN FRAGMENT 0\n" +
-                " OUTPUT EXPRS:<slot 60> | <slot 61> | <slot 62> | <slot 63>\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  RESULT SINK\n" +
-                "\n" +
-                "  47:EXCHANGE\n" +
-                "     tuple ids: 15 \n" +
-                "\n" +
-                "PLAN FRAGMENT 1\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: HASH_PARTITIONED: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 47\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  46:AGGREGATE (merge finalize)\n" +
-                "  |  group by: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  45:EXCHANGE\n" +
-                "     tuple ids: 15 \n" +
-                "\n" +
-                "PLAN FRAGMENT 2\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 45\n" +
-                "    HASH_PARTITIONED: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "\n" +
-                "  23:AGGREGATE (update serialize)\n" +
-                "  |  STREAMING\n" +
-                "  |  group by: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  17:EXCEPT\n" +
-                "  |  child exprs: \n" +
-                "  |      <slot 60> | <slot 61> | <slot 62> | <slot 63>\n" +
-                "  |      <slot 57> | <slot 58> | <slot 56> | <slot 59>\n" +
-                "  |  pass-through-operands: 43\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  |----44:EXCHANGE\n" +
-                "  |       limit: 3\n" +
-                "  |       tuple ids: 14 \n" +
-                "  |    \n" +
-                "  43:EXCHANGE\n" +
-                "     tuple ids: 15 \n" +
-                "\n" +
-                "PLAN FRAGMENT 3\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 44\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  42:MERGING-EXCHANGE\n" +
-                "     limit: 3\n" +
-                "     tuple ids: 14 \n" +
-                "\n" +
-                "PLAN FRAGMENT 4\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 42\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  22:TOP-N\n" +
-                "  |  order by: <slot 56> ASC\n" +
-                "  |  offset: 0\n" +
-                "  |  limit: 3\n" +
-                "  |  tuple ids: 14 \n" +
-                "  |  \n" +
-                "  21:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 52>, <slot 53>, <slot 54>, <slot 55>\n" +
-                "  |  tuple ids: 13 \n" +
-                "  |  \n" +
-                "  18:INTERSECT\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 13 \n" +
-                "  |  \n" +
-                "  |----41:EXCHANGE\n" +
-                "  |       tuple ids: 12 \n" +
-                "  |    \n" +
-                "  40:EXCHANGE\n" +
-                "     tuple ids: 11 \n" +
-                "\n" +
-                "PLAN FRAGMENT 5\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 41\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  20:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 5\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 12 \n" +
-                "\n" +
-                "PLAN FRAGMENT 6\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 40\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  19:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 3\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 11 \n" +
-                "\n" +
-                "PLAN FRAGMENT 7\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: HASH_PARTITIONED: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 43\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  39:AGGREGATE (merge finalize)\n" +
-                "  |  group by: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  38:EXCHANGE\n" +
-                "     tuple ids: 15 \n" +
-                "\n" +
-                "PLAN FRAGMENT 8\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 38\n" +
-                "    HASH_PARTITIONED: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "\n" +
-                "  16:AGGREGATE (update serialize)\n" +
-                "  |  STREAMING\n" +
-                "  |  group by: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  11:UNION\n" +
-                "  |  child exprs: \n" +
-                "  |      <slot 40> | <slot 41> | <slot 42> | <slot 43>\n" +
-                "  |      <slot 60> | <slot 61> | <slot 62> | <slot 63>\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  |----37:EXCHANGE\n" +
-                "  |       tuple ids: 15 \n" +
-                "  |    \n" +
-                "  36:EXCHANGE\n" +
-                "     tuple ids: 10 \n" +
-                "\n" +
-                "PLAN FRAGMENT 9\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: HASH_PARTITIONED: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 37\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  35:AGGREGATE (merge finalize)\n" +
-                "  |  group by: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  34:EXCHANGE\n" +
-                "     tuple ids: 15 \n" +
-                "\n" +
-                "PLAN FRAGMENT 10\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 34\n" +
-                "    HASH_PARTITIONED: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "\n" +
-                "  10:AGGREGATE (update serialize)\n" +
-                "  |  STREAMING\n" +
-                "  |  group by: <slot 60>, <slot 61>, <slot 62>, <slot 63>\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  0:INTERSECT\n" +
-                "  |  child exprs: \n" +
-                "  |      <slot 12> | <slot 13> | <slot 14> | <slot 15>\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      <slot 29> | <slot 30> | <slot 28> | <slot 31>\n" +
-                "  |  pass-through-operands: 31,32\n" +
-                "  |  tuple ids: 15 \n" +
-                "  |  \n" +
-                "  |----32:EXCHANGE\n" +
-                "  |       tuple ids: 0 \n" +
-                "  |    \n" +
-                "  |----33:EXCHANGE\n" +
-                "  |       limit: 3\n" +
-                "  |       tuple ids: 7 \n" +
-                "  |    \n" +
-                "  31:EXCHANGE\n" +
-                "     tuple ids: 3 \n" +
-                "\n" +
-                "PLAN FRAGMENT 11\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: UNPARTITIONED\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 33\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  30:MERGING-EXCHANGE\n" +
-                "     limit: 3\n" +
-                "     tuple ids: 7 \n" +
-                "\n" +
-                "PLAN FRAGMENT 12\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 30\n" +
-                "    UNPARTITIONED\n" +
-                "\n" +
-                "  9:TOP-N\n" +
-                "  |  order by: <slot 28> ASC\n" +
-                "  |  offset: 0\n" +
-                "  |  limit: 3\n" +
-                "  |  tuple ids: 7 \n" +
-                "  |  \n" +
-                "  8:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 24>, <slot 25>, <slot 26>, <slot 27>\n" +
-                "  |  tuple ids: 6 \n" +
-                "  |  \n" +
-                "  5:EXCEPT\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 6 \n" +
-                "  |  \n" +
-                "  |----29:EXCHANGE\n" +
-                "  |       tuple ids: 5 \n" +
-                "  |    \n" +
-                "  28:EXCHANGE\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 13\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 29\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  7:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 3\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 5 \n" +
-                "\n" +
-                "PLAN FRAGMENT 14\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 28\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  6:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 4 \n" +
-                "\n" +
-                "PLAN FRAGMENT 15\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 32\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  1:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'a', `k4` = 1\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 0 \n" +
-                "\n" +
-                "PLAN FRAGMENT 16\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 31\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  2:UNION\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 3 \n" +
-                "  |  \n" +
-                "  |----27:EXCHANGE\n" +
-                "  |       tuple ids: 2 \n" +
-                "  |    \n" +
-                "  26:EXCHANGE\n" +
-                "     tuple ids: 1 \n" +
-                "\n" +
-                "PLAN FRAGMENT 17\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 27\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  4:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 2 \n" +
-                "\n" +
-                "PLAN FRAGMENT 18\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 26\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  3:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 2\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 1 \n" +
-                "\n" +
-                "PLAN FRAGMENT 19\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 36\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  15:AGGREGATE (update finalize)\n" +
-                "  |  group by: <slot 40>, <slot 41>, <slot 42>, <slot 43>\n" +
-                "  |  tuple ids: 10 \n" +
-                "  |  \n" +
-                "  12:INTERSECT\n" +
-                "  |  child exprs: \n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |      `default_cluster:db1.tbl1`.`k1` | `default_cluster:db1.tbl1`.`k2` | `default_cluster:db1.tbl1`.`k3` | `default_cluster:db1.tbl1`.`k4`\n" +
-                "  |  pass-through-operands: all\n" +
-                "  |  tuple ids: 10 \n" +
-                "  |  \n" +
-                "  |----25:EXCHANGE\n" +
-                "  |       tuple ids: 9 \n" +
-                "  |    \n" +
-                "  24:EXCHANGE\n" +
-                "     tuple ids: 8 \n" +
-                "\n" +
-                "PLAN FRAGMENT 20\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 25\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  14:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 4\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 9 \n" +
-                "\n" +
-                "PLAN FRAGMENT 21\n" +
-                " OUTPUT EXPRS:\n" +
-                "  PARTITION: RANDOM\n" +
-                "\n" +
-                "  STREAM DATA SINK\n" +
-                "    EXCHANGE ID: 24\n" +
-                "    RANDOM\n" +
-                "\n" +
-                "  13:OlapScanNode\n" +
-                "     TABLE: tbl1\n" +
-                "     PREAGGREGATION: OFF. Reason: No AggregateInfo\n" +
-                "     PREDICATES: `k1` = 'b', `k4` = 3\n" +
-                "     partitions=0/1\n" +
-                "     rollup: null\n" +
-                "     tabletRatio=0/0\n" +
-                "     tabletList=\n" +
-                "     cardinality=-1\n" +
-                "     avgRowSize=0.0\n" +
-                "     numNodes=0\n" +
-                "     tuple ids: 8 \n";
         StmtExecutor stmtExecutor9 = new StmtExecutor(ctx, sql9);
         stmtExecutor9.execute();
         Planner planner9 = stmtExecutor9.planner();
         List<PlanFragment> fragments9 = planner9.getFragments();
-        Assert.assertEquals(plan9, planner9.getExplainString(fragments9, TExplainLevel.VERBOSE));
+        String plan9 = planner9.getExplainString(fragments9, TExplainLevel.VERBOSE);
+        Assert.assertEquals(2, StringUtils.countMatches(plan9, "UNION"));
+        Assert.assertEquals(3, StringUtils.countMatches(plan9, "INTERSECT"));
+        Assert.assertEquals(2, StringUtils.countMatches(plan9, "EXCEPT"));
     }
 
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org