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

yiguolei pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.0 by this push:
     new 0024b079b53 branch-4.0: [feature] (nereids) generate sql digest for 
nereids prased stmt #56256 (#57818)
0024b079b53 is described below

commit 0024b079b539708283aeb71bf979ca21830eb6b3
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Nov 11 19:01:48 2025 +0800

    branch-4.0: [feature] (nereids) generate sql digest for nereids prased stmt 
#56256 (#57818)
    
    Cherry-picked from #56256
    
    Co-authored-by: HonestManXin <[email protected]>
---
 .../org/apache/doris/analysis/TableSnapshot.java   |   8 +
 .../doris/nereids/analyzer/UnboundAlias.java       |  10 +
 .../doris/nereids/analyzer/UnboundFunction.java    |  14 +
 .../doris/nereids/analyzer/UnboundInlineTable.java |   6 +
 .../nereids/analyzer/UnboundOneRowRelation.java    |  12 +
 .../doris/nereids/analyzer/UnboundRelation.java    |  23 ++
 .../doris/nereids/analyzer/UnboundResultSink.java  |   5 +
 .../apache/doris/nereids/analyzer/UnboundSlot.java |   5 +
 .../apache/doris/nereids/analyzer/UnboundStar.java |  15 ++
 .../doris/nereids/analyzer/UnboundTVFRelation.java |   7 +
 .../doris/nereids/analyzer/UnboundTableSink.java   |  11 +
 .../doris/nereids/analyzer/UnboundVariable.java    |   5 +
 .../doris/nereids/parser/LogicalPlanBuilder.java   |   7 +
 .../apache/doris/nereids/properties/OrderKey.java  |  18 ++
 .../org/apache/doris/nereids/trees/TreeNode.java   |   4 +
 .../doris/nereids/trees/expressions/Alias.java     |  12 +
 .../nereids/trees/expressions/BinaryOperator.java  |   9 +
 .../doris/nereids/trees/expressions/CaseWhen.java  |  14 +
 .../doris/nereids/trees/expressions/Cast.java      |  11 +
 .../trees/expressions/CompoundPredicate.java       |  10 +
 .../trees/expressions/DefaultValueSlot.java        |   5 +
 .../doris/nereids/trees/expressions/Exists.java    |  10 +
 .../nereids/trees/expressions/InPredicate.java     |  25 ++
 .../nereids/trees/expressions/InSubquery.java      |  10 +
 .../doris/nereids/trees/expressions/IsNull.java    |   8 +
 .../doris/nereids/trees/expressions/Not.java       |   7 +
 .../nereids/trees/expressions/OrderExpression.java |   5 +
 .../nereids/trees/expressions/Placeholder.java     |   4 +
 .../nereids/trees/expressions/Properties.java      |   5 +
 .../nereids/trees/expressions/ScalarSubquery.java  |   9 +
 .../trees/expressions/StringRegexPredicate.java    |  11 +
 .../doris/nereids/trees/expressions/Subtract.java  |  13 +
 .../trees/expressions/TimestampArithmetic.java     |  15 ++
 .../nereids/trees/expressions/UnaryOperator.java   |   8 +
 .../nereids/trees/expressions/WhenClause.java      |   7 +
 .../trees/expressions/WindowExpression.java        |  18 ++
 .../nereids/trees/expressions/WindowFrame.java     |  38 +++
 .../trees/expressions/functions/BoundFunction.java |  11 +
 .../functions/agg/AggregateFunction.java           |  14 +
 .../trees/expressions/functions/agg/Count.java     |   8 +
 .../trees/expressions/functions/scalar/Lambda.java |  12 +
 .../trees/expressions/literal/ArrayLiteral.java    |   7 +
 .../trees/expressions/literal/Interval.java        |   9 +
 .../nereids/trees/expressions/literal/Literal.java |   5 +
 .../trees/plans/commands/ExplainCommand.java       |   8 +
 .../trees/plans/commands/ExportCommand.java        |  13 +
 .../insert/BatchInsertIntoTableCommand.java        |   7 +
 .../commands/insert/InsertIntoTableCommand.java    |  11 +
 .../insert/InsertOverwriteTableCommand.java        |  12 +
 .../trees/plans/logical/LogicalAggregate.java      |  68 ++++-
 .../nereids/trees/plans/logical/LogicalCTE.java    |  14 +
 .../trees/plans/logical/LogicalCheckPolicy.java    |   5 +
 .../nereids/trees/plans/logical/LogicalExcept.java |   9 +
 .../trees/plans/logical/LogicalFileSink.java       |  10 +
 .../nereids/trees/plans/logical/LogicalFilter.java |  12 +
 .../trees/plans/logical/LogicalGenerate.java       |  22 ++
 .../nereids/trees/plans/logical/LogicalHaving.java |  13 +
 .../trees/plans/logical/LogicalIntersect.java      |   9 +
 .../nereids/trees/plans/logical/LogicalJoin.java   |  18 ++
 .../nereids/trees/plans/logical/LogicalLimit.java  |  11 +
 .../trees/plans/logical/LogicalProject.java        |  19 ++
 .../trees/plans/logical/LogicalQualify.java        |  10 +
 .../nereids/trees/plans/logical/LogicalRepeat.java |  45 +++-
 .../trees/plans/logical/LogicalSelectHint.java     |   5 +
 .../nereids/trees/plans/logical/LogicalSort.java   |  12 +
 .../trees/plans/logical/LogicalSubQueryAlias.java  |  13 +
 .../nereids/trees/plans/logical/LogicalUnion.java  |   9 +
 .../trees/plans/logical/LogicalUsingJoin.java      |  16 ++
 .../nereids/parser/NereidsParserDigestTest.java    | 300 +++++++++++++++++++++
 69 files changed, 1124 insertions(+), 17 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/TableSnapshot.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/TableSnapshot.java
index efae08263c5..d5a904dc950 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/TableSnapshot.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/TableSnapshot.java
@@ -68,4 +68,12 @@ public class TableSnapshot {
             return " FOR TIME AS OF '" + value + "'";
         }
     }
+
+    public String toDigest() {
+        if (this.type == VersionType.VERSION) {
+            return " FOR VERSION AS OF " + '?';
+        } else {
+            return " FOR TIME AS OF '" + '?' + "'";
+        }
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundAlias.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundAlias.java
index 692f8f28318..232c705df98 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundAlias.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundAlias.java
@@ -104,6 +104,16 @@ public class UnboundAlias extends NamedExpression 
implements UnaryExpression, Un
         return stringBuilder.toString();
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(child().toDigest());
+        if (alias.isPresent()) {
+            sb.append(" AS " + alias.get());
+        }
+        return sb.toString();
+    }
+
     @Override
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitUnboundAlias(this, context);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundFunction.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundFunction.java
index fae76a5c038..666455b7ed7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundFunction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundFunction.java
@@ -130,6 +130,20 @@ public class UnboundFunction extends Function implements 
Unbound, PropagateNulla
         return "'" + getName() + "(" + (isDistinct ? "distinct " : "") + 
params + ")";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getName().toUpperCase());
+        sb.append("(");
+        sb.append(isDistinct ? "distinct " : "");
+        sb.append(
+                children.stream().map(Expression::toDigest)
+                        .collect(Collectors.joining(", "))
+        );
+        sb.append(")");
+        return sb.toString();
+    }
+
     @Override
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitUnboundFunction(this, context);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundInlineTable.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundInlineTable.java
index 42d637d676f..3a6e4be2ec8 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundInlineTable.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundInlineTable.java
@@ -84,4 +84,10 @@ public class UnboundInlineTable extends LogicalLeaf 
implements InlineTable, Bloc
     public List<Slot> computeOutput() {
         throw new UnboundException("output");
     }
+
+    @Override
+    public String toDigest() {
+        // TODO handle exprList?
+        return "VALUES ?";
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundOneRowRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundOneRowRelation.java
index bb61bc93574..1bee39655f2 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundOneRowRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundOneRowRelation.java
@@ -37,6 +37,7 @@ import com.google.common.collect.ImmutableList;
 
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * A relation that contains only one row consist of some constant expressions.
@@ -108,4 +109,15 @@ public class UnboundOneRowRelation extends LogicalRelation 
implements Unbound, O
                 "projects", projects
         );
     }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("SELECT ");
+        sb.append(
+                projects.stream().map(Expression::toDigest)
+                        .collect(Collectors.joining(", "))
+        );
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundRelation.java
index 963986bf383..f02c2bd3b2b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundRelation.java
@@ -42,6 +42,7 @@ import org.apache.commons.lang3.StringUtils;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * Represent a relation plan node that has not been bound.
@@ -187,6 +188,28 @@ public class UnboundRelation extends LogicalRelation 
implements Unbound, BlockFu
         return Utils.toSqlString("UnboundRelation", args.toArray());
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        if (nameParts.size() > 0) {
+            sb.append(nameParts.stream().collect(Collectors.joining(".")));
+            sb.append(" ");
+        }
+        if (indexName.isPresent()) {
+            sb.append("INDEX ").append(indexName.get()).append(" ");
+        }
+        if (tabletIds.size() > 0) {
+            sb.append("TABLET(?)").append(" ");
+        }
+        if (scanParams != null) {
+            sb.append("@").append(scanParams.getParamType()).append(" ");
+        }
+        if (tableSnapshot.isPresent()) {
+            sb.append(tableSnapshot.get().toDigest()).append(" ");
+        }
+        return sb.substring(0, sb.length() - 1);
+    }
+
     @Override
     public boolean equals(Object o) {
         if (o == null || getClass() != o.getClass()) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundResultSink.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundResultSink.java
index e6fd3eedf0d..747049d3d8c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundResultSink.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundResultSink.java
@@ -91,6 +91,11 @@ public class UnboundResultSink<CHILD_TYPE extends Plan> 
extends LogicalSink<CHIL
         return Utils.toSqlString("UnboundResultSink[" + id.asInt() + "]");
     }
 
+    @Override
+    public String toDigest() {
+        return child().toDigest();
+    }
+
     @Override
     public StmtType stmtType() {
         return StmtType.SELECT;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundSlot.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundSlot.java
index 9f60aa2e68e..2181b1cdc5f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundSlot.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundSlot.java
@@ -70,6 +70,11 @@ public class UnboundSlot extends Slot implements Unbound, 
PropagateNullable {
         }).reduce((left, right) -> left + "." + right).orElse("");
     }
 
+    @Override
+    public String toDigest() {
+        return computeToSql();
+    }
+
     @Override
     public List<String> getQualifier() {
         return nameParts.subList(0, nameParts.size() - 1);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundStar.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundStar.java
index a78ca007d6d..4a2985dc281 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundStar.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundStar.java
@@ -156,6 +156,21 @@ public class UnboundStar extends Slot implements 
LeafExpression, Unbound, Propag
         return toSql();
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        if (qualifier.isEmpty()) {
+            sb.append("*");
+        } else {
+            sb.append(qualifier.get(0) + ".*");
+        }
+        if (!exceptedSlots.isEmpty()) {
+            sb.append(exceptedSlots.stream().map(NamedExpression::toDigest)
+                    .collect(Collectors.joining(", ", " EXCEPT(", ")")));
+        }
+        return sb.toString();
+    }
+
     public Optional<Pair<Integer, Integer>> getIndexInSqlString() {
         return indexInSqlString;
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTVFRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTVFRelation.java
index 3024058edc7..ee8212b9049 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTVFRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTVFRelation.java
@@ -107,4 +107,11 @@ public class UnboundTVFRelation extends LogicalRelation 
implements TVFRelation,
                 "arguments", properties
         );
     }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder(functionName);
+        sb.append("(?)");
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSink.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSink.java
index f2b6821c804..b9290c796c2 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSink.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSink.java
@@ -196,4 +196,15 @@ public class UnboundTableSink<CHILD_TYPE extends Plan> 
extends UnboundLogicalSin
                 "colNames", colNames,
                 "hints", hints);
     }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("INSERT INTO ").append(StringUtils.join(nameParts, "."));
+        if (colNames != null && colNames.size() > 0) {
+            sb.append(" (").append(StringUtils.join(colNames, ", 
")).append(")");
+        }
+        sb.append(" ").append(child().toDigest());
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundVariable.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundVariable.java
index 340008bcc0a..66e19e486c1 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundVariable.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundVariable.java
@@ -80,6 +80,11 @@ public class UnboundVariable extends Expression implements 
Unbound {
         }
     }
 
+    @Override
+    public String toDigest() {
+        return toString();
+    }
+
     /**
      * variable type
      */
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index d35222dd2d1..c43718126c6 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -4134,6 +4134,13 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
             List<OrderKey> orderKeys = Lists.newArrayList();
             LogicalPlan aggregate = withAggregate(filter, selectColumnCtx, 
aggClause, orderKeys);
             boolean isDistinct = (selectClause.DISTINCT() != null);
+            if (!isDistinct) {
+                if (aggregate instanceof LogicalRepeat) {
+                    aggregate = ((LogicalRepeat) 
aggregate).withInProjection(false);
+                } else if (aggregate instanceof LogicalAggregate) {
+                    aggregate = ((LogicalAggregate) 
aggregate).withInProjection(false);
+                }
+            }
             LogicalPlan selectPlan;
             if (!(aggregate instanceof Aggregate) && havingClause.isPresent()) 
{
                 // create a project node for pattern match of 
ProjectToGlobalAggregate rule
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/OrderKey.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/OrderKey.java
index e3f06c7d1dd..1e0654aec46 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/OrderKey.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/OrderKey.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.properties;
 
 import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
 
 import java.util.Objects;
 
@@ -78,6 +79,23 @@ public class OrderKey {
         return expr.toString() + (isAsc ? " asc" : " desc") + (nullFirst ? " 
null first" : "");
     }
 
+    /**
+     * generate digest for order by key
+     *
+     * @return String
+     */
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        if (expr instanceof Literal) {
+            sb.append(expr.toString());
+        } else {
+            sb.append(expr.toDigest());
+        }
+        sb.append(isAsc ? " ASC" : " DESC");
+        sb.append(nullFirst ? " NULLS FIRST" : "");
+        return sb.toString();
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(expr, isAsc, nullFirst);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/TreeNode.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/TreeNode.java
index 74215be9c4e..a5e3ee9c5e1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/TreeNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/TreeNode.java
@@ -389,4 +389,8 @@ public interface TreeNode<NODE_TYPE extends 
TreeNode<NODE_TYPE>> {
         // If the "that" tree hasn't been fully traversed, return false.
         return thatDeque.isEmpty();
     }
+
+    default String toDigest() {
+        return "";
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Alias.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Alias.java
index 59d90b6668a..0650a7cbf86 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Alias.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Alias.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.trees.expressions;
 
 import org.apache.doris.nereids.exceptions.UnboundException;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DataType;
@@ -153,6 +154,17 @@ public class Alias extends NamedExpression implements 
UnaryExpression {
         return child().toString() + " AS `" + name.get() + "`#" + exprId;
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        if (child() instanceof Literal) {
+            sb.append("?");
+        } else {
+            sb.append(child().toDigest()).append(" AS ").append(getName());
+        }
+        return sb.toString();
+    }
+
     @Override
     public Alias withChildren(List<Expression> children) {
         Preconditions.checkArgument(children.size() == 1);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/BinaryOperator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/BinaryOperator.java
index c081fe5dc4a..0b8ee94e8b8 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/BinaryOperator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/BinaryOperator.java
@@ -59,6 +59,15 @@ public abstract class BinaryOperator extends Expression 
implements BinaryExpress
         return "(" + left().toString() + " " + symbol + " " + 
right().toString() + ")";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(left().toDigest());
+        sb.append(" ").append(symbol).append(" ");
+        sb.append(right().toDigest());
+        return sb.toString();
+    }
+
     @Override
     public String getFingerprint() {
         String leftFingerprint = left().toString();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/CaseWhen.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/CaseWhen.java
index 0c3687f5715..cf301859987 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/CaseWhen.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/CaseWhen.java
@@ -110,6 +110,20 @@ public class CaseWhen extends Expression {
         return output.toString();
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder("CASE");
+        for (Expression child : children()) {
+            if (child instanceof WhenClause) {
+                sb.append(child.toDigest());
+            } else {
+                sb.append(" ELSE ").append(child.toDigest());
+            }
+        }
+        sb.append(" END");
+        return sb.toString();
+    }
+
     @Override
     public String computeToSql() throws UnboundException {
         StringBuilder output = new StringBuilder("CASE");
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java
index edff4a5e9d0..69280d38321 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java
@@ -221,6 +221,17 @@ public class Cast extends Expression implements 
UnaryExpression, Monotonic {
         return "cast(" + child() + " as " + targetType + ")";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("cast(")
+                .append(child().toDigest())
+                .append(" as ")
+                .append(targetType)
+                .append(")");
+        return sb.toString();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (!super.equals(o)) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/CompoundPredicate.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/CompoundPredicate.java
index 8b08fd39c2e..13f99839ffb 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/CompoundPredicate.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/CompoundPredicate.java
@@ -116,6 +116,16 @@ public abstract class CompoundPredicate extends Expression 
implements ExpectsInp
         return symbol + "[" + sb + "]";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("(");
+        sb.append(children().stream().map(c -> c.toDigest())
+                .collect(Collectors.joining(" " + symbol + " ")));
+        sb.append(")");
+        return sb.toString();
+    }
+
     @Override
     public String getFingerprint() {
         StringBuilder sb = new StringBuilder();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/DefaultValueSlot.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/DefaultValueSlot.java
index a66428dc982..56d7790fd92 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/DefaultValueSlot.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/DefaultValueSlot.java
@@ -64,6 +64,11 @@ public class DefaultValueSlot extends Slot {
         return "DEFAULT_VALUE";
     }
 
+    @Override
+    public String toDigest() {
+        return "?";
+    }
+
     public Slot withIndexInSql(Pair<Integer, Integer> index) {
         return this;
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Exists.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Exists.java
index c95882a71e2..80989377d69 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Exists.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Exists.java
@@ -75,6 +75,16 @@ public class Exists extends SubqueryExpr implements 
LeafExpression {
         return "EXISTS (SUBQUERY) " + super.toString();
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(isNot ? "NOT " : "")
+                .append("EXISTS (")
+                .append(queryPlan.toDigest())
+                .append(")");
+        return sb.toString();
+    }
+
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitExistsSubquery(this, context);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/InPredicate.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/InPredicate.java
index 81c0bd7a839..df972af1832 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/InPredicate.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/InPredicate.java
@@ -179,6 +179,31 @@ public class InPredicate extends Expression {
             .collect(Collectors.joining(", ", "(", ")"));
     }
 
+    private boolean isLiteralOptions() {
+        for (Expression option : options) {
+            if (!(option instanceof Literal)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(compareExpr.toDigest());
+        sb.append(" IN ");
+        if (isLiteralOptions()) {
+            sb.append("(?)");
+        } else {
+            sb.append(
+                    options.stream().map(Expression::toDigest)
+                            .collect(Collectors.joining(", ", "(", ")"))
+            );
+        }
+        return sb.toString();
+    }
+
     @Override
     public String getFingerprint() {
         return compareExpr + " IN " + options.stream()
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/InSubquery.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/InSubquery.java
index 6b77700a4c8..20d327dcab1 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/InSubquery.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/InSubquery.java
@@ -78,6 +78,16 @@ public class InSubquery extends SubqueryExpr implements 
UnaryExpression {
         return this.child() + " IN (INSUBQUERY) " + super.toString();
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getCompareExpr().toDigest());
+        sb.append(isNot ? " NOT IN (" : " IN (");
+        sb.append(queryPlan.toDigest());
+        sb.append(')');
+        return sb.toString();
+    }
+
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitInSubquery(this, context);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/IsNull.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/IsNull.java
index a5ea5fd9495..a587ac6f8e2 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/IsNull.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/IsNull.java
@@ -64,6 +64,14 @@ public class IsNull extends Expression implements 
UnaryExpression, AlwaysNotNull
         return child().toString() + " IS NULL";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(child().toDigest());
+        sb.append(" IS NULL");
+        return sb.toString();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (!super.equals(o)) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Not.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Not.java
index 25602f08e8a..12c0252d3a3 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Not.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Not.java
@@ -101,6 +101,13 @@ public class Not extends Expression implements 
UnaryExpression, ExpectsInputType
         return "( not " + child().toString() + ")";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("NOT ").append(child().toDigest());
+        return sb.toString();
+    }
+
     @Override
     public String computeToSql() {
         return "( not " + child().toSql() + ")";
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/OrderExpression.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/OrderExpression.java
index b0564b70737..dffac46e5b9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/OrderExpression.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/OrderExpression.java
@@ -80,6 +80,11 @@ public class OrderExpression extends Expression implements 
UnaryExpression, Prop
         return orderKey.toString();
     }
 
+    @Override
+    public String toDigest() {
+        return orderKey.toDigest();
+    }
+
     @Override
     public String computeToSql() {
         return orderKey.toSql();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Placeholder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Placeholder.java
index caa3fd07e30..ce92a0e36c8 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Placeholder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Placeholder.java
@@ -66,6 +66,10 @@ public class Placeholder extends Expression implements 
LeafExpression {
         return "$" + placeholderId.asInt();
     }
 
+    public String toDigest() {
+        return "?";
+    }
+
     @Override
     public String computeToSql() {
         return "?";
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Properties.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Properties.java
index 09dc6a5c216..afffa324424 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Properties.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Properties.java
@@ -69,6 +69,11 @@ public class Properties extends Expression implements 
LeafExpression {
         return "Properties(" + toSql() + ")";
     }
 
+    @Override
+    public String toDigest() {
+        return "?";
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ScalarSubquery.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ScalarSubquery.java
index ed608135254..6e608ee5e4d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ScalarSubquery.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ScalarSubquery.java
@@ -113,6 +113,15 @@ public class ScalarSubquery extends SubqueryExpr 
implements LeafExpression {
         return " (SCALARSUBQUERY) " + super.toString();
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("(")
+                .append(queryPlan.toDigest())
+                .append(")");
+        return sb.toString();
+    }
+
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitScalarSubquery(this, context);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/StringRegexPredicate.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/StringRegexPredicate.java
index 5a62be54f93..21833fe8780 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/StringRegexPredicate.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/StringRegexPredicate.java
@@ -64,6 +64,17 @@ public abstract class StringRegexPredicate extends 
ScalarFunction
         return "(" + left() + " " + getName() + " " + right() + ")";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(left().toDigest())
+                .append(' ')
+                .append(getName())
+                .append(' ')
+                .append(right().toDigest());
+        return sb.toString();
+    }
+
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitStringRegexPredicate(this, context);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Subtract.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Subtract.java
index 35f4358c79f..46e8174a2db 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Subtract.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Subtract.java
@@ -19,6 +19,7 @@ package org.apache.doris.nereids.trees.expressions;
 
 import org.apache.doris.analysis.ArithmeticExpr.Operator;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DecimalV3Type;
 
@@ -57,4 +58,16 @@ public class Subtract extends BinaryArithmetic implements 
PropagateNullable {
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitSubtract(this, context);
     }
+
+    @Override
+    public String toDigest() {
+        if (left() instanceof IntegerLiteral) {
+            IntegerLiteral left = (IntegerLiteral) left();
+            if (left.getValue() == 0) {
+                // nereids parser change - operator to subtract, so 
compactible with that
+                return " -" + right().toDigest();
+            }
+        }
+        return super.toDigest();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/TimestampArithmetic.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/TimestampArithmetic.java
index 2bf6dc501aa..b028df70c24 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/TimestampArithmetic.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/TimestampArithmetic.java
@@ -129,6 +129,21 @@ public class TimestampArithmetic extends Expression
         return toSql();
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        if (funcName != null) {
+            sb.append(funcName.toUpperCase()).append("(");
+            sb.append(child(0).toDigest()).append(", ");
+            sb.append(child(1).toDigest()).append(")");
+        } else {
+            sb.append(child(0).toDigest());
+            sb.append(" ").append(op.toString()).append(" ");
+            sb.append(child(1).toDigest());
+        }
+        return sb.toString();
+    }
+
     @Override
     public String computeToSql() {
         StringBuilder strBuilder = new StringBuilder();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/UnaryOperator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/UnaryOperator.java
index 299e00d0dc4..e881e0584fe 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/UnaryOperator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/UnaryOperator.java
@@ -55,6 +55,14 @@ public abstract class UnaryOperator extends Expression 
implements UnaryExpressio
         return "(" + symbol + " " + child().toString() + ")";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(" ").append(symbol).append(" ");
+        sb.append(child().toDigest());
+        return sb.toString();
+    }
+
     @Override
     public int computeHashCode() {
         return Objects.hash(symbol, child());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WhenClause.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WhenClause.java
index 6e10701faaa..3a458fc8380 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WhenClause.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WhenClause.java
@@ -98,4 +98,11 @@ public class WhenClause extends Expression implements 
BinaryExpression, ExpectsI
     public String toString() {
         return " WHEN " + left().toString() + " THEN " + right().toString();
     }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(" WHEN ").append(left().toDigest()).append(" THEN 
").append(right().toDigest());
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WindowExpression.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WindowExpression.java
index 659e4066113..fa9337598d2 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WindowExpression.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WindowExpression.java
@@ -227,6 +227,24 @@ public class WindowExpression extends Expression {
         return sb.toString().trim();
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(function.toDigest()).append(" OVER (");
+        if (!partitionKeys.isEmpty()) {
+            sb.append("PARTITION BY ").append(partitionKeys.stream()
+                    .map(Expression::toDigest)
+                    .collect(Collectors.joining(", ", "", " ")));
+        }
+        if (!orderKeys.isEmpty()) {
+            sb.append("ORDER BY ").append(orderKeys.stream()
+                    .map(OrderExpression::toDigest)
+                    .collect(Collectors.joining(", ", "", " ")));
+        }
+        windowFrame.ifPresent(wf -> sb.append(" ").append(wf.toDigest()));
+        return sb.toString().trim() + ")";
+    }
+
     @Override
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitWindow(this, context);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WindowFrame.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WindowFrame.java
index 2c0d7d65b80..b61b3ef1a43 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WindowFrame.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/WindowFrame.java
@@ -119,6 +119,18 @@ public class WindowFrame extends Expression implements 
PropagateNullable, LeafEx
         return sb.toString();
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(frameUnits + " ");
+        if (rightBoundary != null) {
+            sb.append("BETWEEN " + leftBoundary.toDigest() + " AND " + 
rightBoundary.toDigest());
+        } else {
+            sb.append(leftBoundary.toDigest());
+        }
+        return sb.toString();
+    }
+
     @Override
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitWindowFrame(this, context);
@@ -215,6 +227,32 @@ public class WindowFrame extends Expression implements 
PropagateNullable, LeafEx
             return sb.toString();
         }
 
+        /** to digest */
+        public String toDigest() {
+            StringBuilder sb = new StringBuilder();
+            boundOffset.ifPresent(value -> 
sb.append(value.toDigest()).append(" "));
+            switch (frameBoundType) {
+                case UNBOUNDED_PRECEDING:
+                    sb.append("UNBOUNDED PRECEDING");
+                    break;
+                case UNBOUNDED_FOLLOWING:
+                    sb.append("UNBOUNDED FOLLOWING");
+                    break;
+                case CURRENT_ROW:
+                    sb.append("CURRENT ROW");
+                    break;
+                case PRECEDING:
+                    sb.append("PRECEDING");
+                    break;
+                case FOLLOWING:
+                    sb.append("FOLLOWING");
+                    break;
+                default:
+                    break;
+            }
+            return sb.toString();
+        }
+
         /** toSql*/
         public String toSql() {
             StringBuilder sb = new StringBuilder();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BoundFunction.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BoundFunction.java
index 2cd9b27a0b9..16d7740cf9c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BoundFunction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BoundFunction.java
@@ -108,6 +108,17 @@ public abstract class BoundFunction extends Function 
implements ComputeSignature
         return getName() + "(" + args + ")";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getName().toUpperCase());
+        sb.append(
+                children().stream().map(Expression::toDigest)
+                        .collect(Collectors.joining(", ", "(", ")"))
+        );
+        return sb.toString();
+    }
+
     @Override
     public Expression withChildren(List<Expression> children) {
         throw new UnsupportedOperationException(
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/AggregateFunction.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/AggregateFunction.java
index a7bb33f3924..41ed2bbb2c6 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/AggregateFunction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/AggregateFunction.java
@@ -157,6 +157,20 @@ public abstract class AggregateFunction extends 
BoundFunction implements Expects
         return getName() + "(" + (distinct ? "DISTINCT " : "") + args + ")";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder(getName()).append("(");
+        if (distinct) {
+            sb.append("DISTINCT ");
+        }
+        sb.append(
+                children.stream().map(Expression::toDigest)
+                        .collect(Collectors.joining(", "))
+        );
+        sb.append(")");
+        return sb.toString();
+    }
+
     public boolean supportAggregatePhase(AggregatePhase aggregatePhase) {
         return true;
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/Count.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/Count.java
index efbfaaf6bb9..175c25c7ce4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/Count.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/Count.java
@@ -148,6 +148,14 @@ public class Count extends NotNullableAggregateFunction
         return super.toString();
     }
 
+    @Override
+    public String toDigest() {
+        if (isStar) {
+            return "count(*)";
+        }
+        return super.toDigest();
+    }
+
     @Override
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitCount(this, context);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Lambda.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Lambda.java
index 2ecab6090d8..35463aa829b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Lambda.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Lambda.java
@@ -153,6 +153,18 @@ public class Lambda extends Expression {
         return builder.toString();
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        if (argumentNames.size() == 1) {
+            sb.append(argumentNames.get(0));
+        } else {
+            sb.append(argumentNames.stream().collect(Collectors.joining(", ", 
"(", ")")));
+        }
+        sb.append(" -> ").append(getLambdaFunction().toDigest());
+        return sb.toString();
+    }
+
     @Override
     public Lambda withChildren(List<Expression> children) {
         return new Lambda(argumentNames, children);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/ArrayLiteral.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/ArrayLiteral.java
index 57c4bba82e6..e8ff885a5da 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/ArrayLiteral.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/ArrayLiteral.java
@@ -126,6 +126,13 @@ public class ArrayLiteral extends Literal implements 
ComparableLiteral {
         return "[" + items + "]";
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[?]");
+        return sb.toString();
+    }
+
     @Override
     public String computeToSql() {
         String items = this.items.stream()
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Interval.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Interval.java
index 110eb225dea..c5619179291 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Interval.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Interval.java
@@ -70,6 +70,15 @@ public class Interval extends Expression implements 
UnaryExpression, AlwaysNotNu
         return visitor.visitInterval(this, context);
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("INTERVAL ");
+        sb.append(value().toDigest());
+        sb.append(" ").append(timeUnit);
+        return sb.toString();
+    }
+
     /**
      * Supported time unit.
      */
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Literal.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Literal.java
index dc4a0870dc5..21708dc6cd3 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Literal.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Literal.java
@@ -810,4 +810,9 @@ public abstract class Literal extends Expression implements 
LeafExpression {
         }
         return null;
     }
+
+    @Override
+    public String toDigest() {
+        return "?";
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExplainCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExplainCommand.java
index 8d8ece4fe06..c13b284e2b4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExplainCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExplainCommand.java
@@ -133,4 +133,12 @@ public class ExplainCommand extends Command implements 
NoForward {
     public StmtType stmtType() {
         return StmtType.EXPLAIN;
     }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("EXPLAIN ");
+        sb.append(logicalPlan.toDigest());
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExportCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExportCommand.java
index e1ee813224c..69ff31b5474 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExportCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExportCommand.java
@@ -60,6 +60,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 /**
  * EXPORT statement, export data to dirs by broker.
@@ -414,5 +415,17 @@ public class ExportCommand extends Command implements 
NeedAuditEncryption, Forwa
     public boolean needAuditEncryption() {
         return true;
     }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder("EXPORT TABLE ");
+        sb.append(nameParts.stream().collect(Collectors.joining(".")));
+        if (expr.isPresent()) {
+            sb.append(" WHERE ")
+                    .append(expr.get().toDigest());
+        }
+        sb.append(" TO ?");
+        return sb.toString();
+    }
 }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/BatchInsertIntoTableCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/BatchInsertIntoTableCommand.java
index 4fb42a21fd7..b99eb39e296 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/BatchInsertIntoTableCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/BatchInsertIntoTableCommand.java
@@ -257,4 +257,11 @@ public class BatchInsertIntoTableCommand extends Command 
implements NoForward, E
     public StmtType stmtType() {
         return StmtType.INSERT;
     }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(originLogicalQuery.toDigest());
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java
index e618d1dffbc..cb5dfa61de0 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java
@@ -688,4 +688,15 @@ public class InsertIntoTableCommand extends Command 
implements NeedAuditEncrypti
             this.physicalSink = physicalSink;
         }
     }
+
+    @Override
+    public String toDigest() {
+        // if with cte, query will be print twice
+        StringBuilder sb = new StringBuilder();
+        sb.append(originLogicalQuery.toDigest());
+        if (cte.isPresent()) {
+            sb.append(" ").append(cte.get().toDigest());
+        }
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertOverwriteTableCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertOverwriteTableCommand.java
index 1f0c8924f7b..9813c6d0894 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertOverwriteTableCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertOverwriteTableCommand.java
@@ -443,4 +443,16 @@ public class InsertOverwriteTableCommand extends Command 
implements NeedAuditEnc
     public boolean needAuditEncryption() {
         return originLogicalQuery.anyMatch(node -> node instanceof 
TVFRelation);
     }
+
+    @Override
+    public String toDigest() {
+        // if with cte, query will be print twice
+        StringBuilder sb = new StringBuilder();
+        sb.append("OVERWRITE TABLE "); // there is no way add overwrite flag 
in sink(logic query), so add it here
+        sb.append(originLogicalQuery.toDigest());
+        if (cte.isPresent()) {
+            sb.append(" (").append(cte.get().toDigest()).append(")");
+        }
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalAggregate.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalAggregate.java
index 91ba406bc74..362f5082563 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalAggregate.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalAggregate.java
@@ -31,6 +31,7 @@ import 
org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunctio
 import org.apache.doris.nereids.trees.expressions.functions.agg.AggregatePhase;
 import org.apache.doris.nereids.trees.expressions.functions.agg.Count;
 import org.apache.doris.nereids.trees.expressions.functions.agg.Ndv;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.algebra.Aggregate;
@@ -77,6 +78,7 @@ public class LogicalAggregate<CHILD_TYPE extends Plan>
     private final boolean ordinalIsResolved;
     private final boolean generated;
     private final boolean hasPushed;
+    private final boolean withInProjection;
 
     /**
      * Desc: Constructor for LogicalAggregate.
@@ -93,19 +95,20 @@ public class LogicalAggregate<CHILD_TYPE extends Plan>
      * Distinct Agg
      */
     public LogicalAggregate(List<NamedExpression> namedExpressions, boolean 
generated, CHILD_TYPE child) {
-        this(ImmutableList.copyOf(namedExpressions), namedExpressions, false, 
true, generated, false, Optional.empty(),
+        this(ImmutableList.copyOf(namedExpressions), namedExpressions,
+                false, true, generated, false, true, Optional.empty(),
                 Optional.empty(), Optional.empty(), child);
     }
 
     public LogicalAggregate(List<NamedExpression> namedExpressions, boolean 
generated, boolean hasPushed,
             CHILD_TYPE child) {
-        this(ImmutableList.copyOf(namedExpressions), namedExpressions, false, 
true, generated, hasPushed,
+        this(ImmutableList.copyOf(namedExpressions), namedExpressions, false, 
true, generated, hasPushed, true,
                 Optional.empty(), Optional.empty(), Optional.empty(), child);
     }
 
     public LogicalAggregate(List<Expression> groupByExpressions,
             List<NamedExpression> outputExpressions, boolean 
ordinalIsResolved, CHILD_TYPE child) {
-        this(groupByExpressions, outputExpressions, false, ordinalIsResolved, 
false, false, Optional.empty(),
+        this(groupByExpressions, outputExpressions, false, ordinalIsResolved, 
false, false, true, Optional.empty(),
                 Optional.empty(), Optional.empty(), child);
     }
 
@@ -127,7 +130,7 @@ public class LogicalAggregate<CHILD_TYPE extends Plan>
             boolean normalized,
             Optional<LogicalRepeat<?>> sourceRepeat,
             CHILD_TYPE child) {
-        this(groupByExpressions, outputExpressions, normalized, false, false, 
false, sourceRepeat,
+        this(groupByExpressions, outputExpressions, normalized, false, false, 
false, true, sourceRepeat,
                 Optional.empty(), Optional.empty(), child);
     }
 
@@ -141,6 +144,7 @@ public class LogicalAggregate<CHILD_TYPE extends Plan>
             boolean ordinalIsResolved,
             boolean generated,
             boolean hasPushed,
+            boolean withInProjection,
             Optional<LogicalRepeat<?>> sourceRepeat,
             Optional<GroupExpression> groupExpression,
             Optional<LogicalProperties> logicalProperties,
@@ -157,6 +161,7 @@ public class LogicalAggregate<CHILD_TYPE extends Plan>
         this.generated = generated;
         this.hasPushed = hasPushed;
         this.sourceRepeat = Objects.requireNonNull(sourceRepeat, "sourceRepeat 
cannot be null");
+        this.withInProjection = withInProjection;
     }
 
     @Override
@@ -199,6 +204,33 @@ public class LogicalAggregate<CHILD_TYPE extends Plan>
         );
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        // org.apache.doris.nereids.parser.LogicalPlanBuilder.withProjection 
will generate different plan for
+        // distinct aggregation so use withInProjection flag to control 
whether to generate a select statement
+        // eg: select distinct type, id from tb_book group by type;
+        // select type, id from tb_book group by type;
+        if (!withInProjection) {
+            sb.append("SELECT ");
+            sb.append(
+                    outputExpressions.stream().map(Expression::toDigest)
+                            .collect(Collectors.joining(", "))
+            );
+            sb.append(" FROM ");
+        }
+        sb.append(child().toDigest());
+        sb.append(" GROUP BY ");
+        sb.append(
+                groupByExpressions.stream()
+                        .map(it ->
+                                (it instanceof Literal) ? it.toString() : 
it.toDigest()
+                        )
+                        .collect(Collectors.joining(", "))
+        );
+        return sb.toString();
+    }
+
     @Override
     public String getFingerprint() {
         StringBuilder builder = new StringBuilder();
@@ -284,13 +316,14 @@ public class LogicalAggregate<CHILD_TYPE extends Plan>
     public LogicalAggregate<Plan> withChildren(List<Plan> children) {
         Preconditions.checkArgument(children.size() == 1);
         return new LogicalAggregate<>(groupByExpressions, outputExpressions, 
normalized, ordinalIsResolved, generated,
-                hasPushed, sourceRepeat, Optional.empty(), Optional.empty(), 
children.get(0));
+                hasPushed, withInProjection, sourceRepeat, Optional.empty(), 
Optional.empty(), children.get(0));
     }
 
     @Override
     public LogicalAggregate<Plan> 
withGroupExpression(Optional<GroupExpression> groupExpression) {
         return new LogicalAggregate<>(groupByExpressions, outputExpressions, 
normalized, ordinalIsResolved, generated,
-                hasPushed, sourceRepeat, groupExpression, 
Optional.of(getLogicalProperties()), children.get(0));
+                hasPushed, withInProjection,
+                sourceRepeat, groupExpression, 
Optional.of(getLogicalProperties()), children.get(0));
     }
 
     @Override
@@ -298,30 +331,31 @@ public class LogicalAggregate<CHILD_TYPE extends Plan>
             Optional<LogicalProperties> logicalProperties, List<Plan> 
children) {
         Preconditions.checkArgument(children.size() == 1);
         return new LogicalAggregate<>(groupByExpressions, outputExpressions, 
normalized, ordinalIsResolved, generated,
-                hasPushed, sourceRepeat, groupExpression, 
Optional.of(getLogicalProperties()), children.get(0));
+                hasPushed, withInProjection,
+                sourceRepeat, groupExpression, 
Optional.of(getLogicalProperties()), children.get(0));
     }
 
     public LogicalAggregate<Plan> withGroupByAndOutput(List<Expression> 
groupByExprList,
             List<NamedExpression> outputExpressionList) {
         return new LogicalAggregate<>(groupByExprList, outputExpressionList, 
normalized, ordinalIsResolved, generated,
-                hasPushed, sourceRepeat, Optional.empty(), Optional.empty(), 
child());
+                hasPushed, withInProjection, sourceRepeat, Optional.empty(), 
Optional.empty(), child());
     }
 
     public LogicalAggregate<Plan> withGroupBy(List<Expression> 
groupByExprList) {
         return new LogicalAggregate<>(groupByExprList, outputExpressions, 
normalized, ordinalIsResolved, generated,
-                hasPushed, sourceRepeat, Optional.empty(), Optional.empty(), 
child());
+                hasPushed, withInProjection, sourceRepeat, Optional.empty(), 
Optional.empty(), child());
     }
 
     public LogicalAggregate<Plan> withChildGroupByAndOutput(List<Expression> 
groupByExprList,
             List<NamedExpression> outputExpressionList, Plan newChild) {
         return new LogicalAggregate<>(groupByExprList, outputExpressionList, 
normalized, ordinalIsResolved, generated,
-                hasPushed, sourceRepeat, Optional.empty(), Optional.empty(), 
newChild);
+                hasPushed, withInProjection, sourceRepeat, Optional.empty(), 
Optional.empty(), newChild);
     }
 
     public LogicalAggregate<Plan> withChildAndOutput(CHILD_TYPE child,
                                                        List<NamedExpression> 
outputExpressionList) {
         return new LogicalAggregate<>(groupByExpressions, 
outputExpressionList, normalized, ordinalIsResolved,
-                generated, hasPushed, sourceRepeat, Optional.empty(),
+                generated, hasPushed, withInProjection, sourceRepeat, 
Optional.empty(),
                 Optional.empty(), child);
     }
 
@@ -333,18 +367,24 @@ public class LogicalAggregate<CHILD_TYPE extends Plan>
     @Override
     public LogicalAggregate<CHILD_TYPE> withAggOutput(List<NamedExpression> 
newOutput) {
         return new LogicalAggregate<>(groupByExpressions, newOutput, 
normalized, ordinalIsResolved, generated,
-                hasPushed, sourceRepeat, Optional.empty(), Optional.empty(), 
child());
+                hasPushed, withInProjection, sourceRepeat, Optional.empty(), 
Optional.empty(), child());
     }
 
     public LogicalAggregate<Plan> withAggOutputChild(List<NamedExpression> 
newOutput, Plan newChild) {
         return new LogicalAggregate<>(groupByExpressions, newOutput, 
normalized, ordinalIsResolved, generated,
-                hasPushed, sourceRepeat, Optional.empty(), Optional.empty(), 
newChild);
+                hasPushed, withInProjection, sourceRepeat, Optional.empty(), 
Optional.empty(), newChild);
     }
 
     public LogicalAggregate<Plan> withNormalized(List<Expression> 
normalizedGroupBy,
             List<NamedExpression> normalizedOutput, Plan normalizedChild) {
         return new LogicalAggregate<>(normalizedGroupBy, normalizedOutput, 
true, ordinalIsResolved, generated,
-                hasPushed, sourceRepeat, Optional.empty(), Optional.empty(), 
normalizedChild);
+                hasPushed, withInProjection, sourceRepeat, Optional.empty(), 
Optional.empty(), normalizedChild);
+    }
+
+    public LogicalAggregate<Plan> withInProjection(boolean withInProjection) {
+        return new LogicalAggregate<>(groupByExpressions, outputExpressions, 
normalized, ordinalIsResolved,
+                generated, hasPushed, withInProjection,
+                sourceRepeat, Optional.empty(), Optional.empty(), child());
     }
 
     public LogicalAggregate<Plan> withSourceRepeat(LogicalRepeat<?> 
sourceRepeat) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTE.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTE.java
index 7a75dd5c1a4..4f810c3b6de 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTE.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTE.java
@@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * Logical Node for CTE
@@ -77,6 +78,19 @@ public class LogicalCTE<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE
         );
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("WITH\n");
+        sb.append(
+                aliasQueries.stream().map(LogicalSubQueryAlias::toDigest)
+                        .collect(Collectors.joining(", "))
+        );
+        sb.append("\n");
+        sb.append(child().toDigest());
+        return sb.toString();
+    }
+
     @Override
     public boolean displayExtraPlanFirst() {
         return true;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java
index b0ae1e1c926..750e36d41a6 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java
@@ -90,6 +90,11 @@ public class LogicalCheckPolicy<CHILD_TYPE extends Plan> 
extends LogicalUnary<CH
         return Utils.toSqlString("LogicalCheckPolicy");
     }
 
+    @Override
+    public String toDigest() {
+        return child().toDigest();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalExcept.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalExcept.java
index ec99833c39e..253fff791ec 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalExcept.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalExcept.java
@@ -67,6 +67,15 @@ public class LogicalExcept extends LogicalSetOperation {
                 "stats", statistics);
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("(").append(child(0).toDigest()).append(")");
+        sb.append(" EXCEPT ").append(qualifier).append(" ");
+        sb.append("(").append(child(1).toDigest()).append(")");
+        return sb.toString();
+    }
+
     @Override
     public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
         return visitor.visitLogicalExcept(this, context);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileSink.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileSink.java
index f5e49cdba9e..47a1ed6ff5b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileSink.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileSink.java
@@ -126,4 +126,14 @@ public class LogicalFileSink<CHILD_TYPE extends Plan> 
extends LogicalSink<CHILD_
     public boolean needAuditEncryption() {
         return true;
     }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder(child().toDigest());
+        sb.append(" INTO OUTFILE '").append(" ? ").append(" FORMAT AS 
").append(" ? ");
+        if (properties != null && !properties.isEmpty()) {
+            sb.append(" PROPERTIES(").append(" ? ").append(")");
+        }
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFilter.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFilter.java
index 03515a7d384..b7d72b3476b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFilter.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFilter.java
@@ -119,6 +119,18 @@ public class LogicalFilter<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_T
         );
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(child().toDigest());
+        sb.append(" WHERE ");
+        sb.append(
+                conjuncts.stream().map(Expression::toDigest)
+                        .collect(Collectors.joining(" AND "))
+        );
+        return sb.toString();
+    }
+
     @Override
     public String getFingerprint() {
         return Utils.toSqlString("Filter[" + getGroupIdWithPrefix() + "]",
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalGenerate.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalGenerate.java
index 38dccfbcb98..974317f1942 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalGenerate.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalGenerate.java
@@ -38,6 +38,7 @@ import com.google.common.collect.Lists;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * plan for table generator, the statement like: SELECT * FROM tbl LATERAL 
VIEW EXPLODE(c1) g as (gc1);
@@ -147,6 +148,27 @@ public class LogicalGenerate<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD
         );
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        String generateName = "";
+        try {
+            generateName = generatorOutput.get(0).getQualifier().get(0);
+        } catch (Throwable e) {
+            generateName = generatorOutput.get(0).toDigest();
+        }
+        sb.append(child().toDigest());
+        sb.append(" LATERAL VIEW ")
+                .append(generators.get(0).toDigest())
+                .append(" ")
+                .append(generateName)
+                .append(" AS ")
+                .append(
+                        
expandColumnAlias.get(0).stream().collect(Collectors.joining(", "))
+                );
+        return sb.toString();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHaving.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHaving.java
index 3847ffdf10f..339a2bb046e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHaving.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHaving.java
@@ -39,6 +39,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Logical Having plan
@@ -157,4 +158,16 @@ public class LogicalHaving<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_T
         return Utils.toSqlStringSkipNull("LogicalHaving",
                 "predicates", getPredicate(), "stats", statistics);
     }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(child().toDigest());
+        sb.append(" HAVING ");
+        sb.append(
+                conjuncts.stream().map(Expression::toDigest)
+                        .collect(Collectors.joining(" AND "))
+        );
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalIntersect.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalIntersect.java
index f3e9f9db3f1..c8bce982397 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalIntersect.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalIntersect.java
@@ -67,6 +67,15 @@ public class LogicalIntersect extends LogicalSetOperation {
                 "stats", statistics);
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("(").append(child(0).toDigest()).append(")");
+        sb.append(" INTERSECT ").append(qualifier).append(" ");
+        sb.append("(").append(child(1).toDigest()).append(")");
+        return sb.toString();
+    }
+
     @Override
     public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
         return visitor.visitLogicalIntersect(this, context);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalJoin.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalJoin.java
index b226c4fd626..f7bf44b5518 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalJoin.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalJoin.java
@@ -314,6 +314,24 @@ public class LogicalJoin<LEFT_CHILD_TYPE extends Plan, 
RIGHT_CHILD_TYPE extends
         return Utils.toSqlStringSkipNull("LogicalJoin[" + id.asInt() + "]", 
args.toArray());
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(left().toDigest());
+        sb.append(" ").append(joinType).append(" ");
+        sb.append(right().toDigest());
+        if (!hashJoinConjuncts.isEmpty() || !otherJoinConjuncts.isEmpty()) {
+            sb.append(" ON ");
+            sb.append(
+                    
hashJoinConjuncts.stream().map(Expression::toDigest).collect(Collectors.joining("
 AND "))
+            );
+            sb.append(
+                    
otherJoinConjuncts.stream().map(Expression::toDigest).collect(Collectors.joining("
 AND "))
+            );
+        }
+        return sb.toString();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLimit.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLimit.java
index 32c6705f60e..e651fb75272 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLimit.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLimit.java
@@ -93,6 +93,17 @@ public class LogicalLimit<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TY
         );
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(child().toDigest());
+        sb.append(" LIMIT ? ");
+        if (offset != 0) {
+            sb.append(" OFFSET ?");
+        }
+        return sb.toString();
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(limit, offset);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalProject.java
index 6af69f001ae..6925bcf8fe7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalProject.java
@@ -51,6 +51,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Logical project plan.
@@ -124,6 +125,24 @@ public class LogicalProject<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_
         );
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("SELECT ");
+        if (isDistinct) {
+            sb.append("DISTINCT ");
+        }
+        sb.append(
+                projects.stream().map(NamedExpression::toDigest)
+                        .collect(Collectors.joining(", "))
+        );
+        if (child().getType() != PlanType.LOGICAL_UNBOUND_ONE_ROW_RELATION) {
+            sb.append(" FROM ");
+        }
+        sb.append(child().toDigest());
+        return sb.toString();
+    }
+
     @Override
     public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
         return visitor.visitLogicalProject(this, context);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalQualify.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalQualify.java
index 904c66f6482..665990b3fdc 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalQualify.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalQualify.java
@@ -39,6 +39,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Logical qualify plan.
@@ -90,6 +91,15 @@ public class LogicalQualify<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_
         );
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(child().toDigest());
+        sb.append(" QUALIFY ");
+        
sb.append(conjuncts.stream().map(Expression::toDigest).collect(Collectors.joining("
 AND ")));
+        return sb.toString();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalRepeat.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalRepeat.java
index 23c8be7417c..f2b4a18e46b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalRepeat.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalRepeat.java
@@ -39,6 +39,7 @@ import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * LogicalRepeat.
@@ -51,6 +52,7 @@ public class LogicalRepeat<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_T
 
     private final List<List<Expression>> groupingSets;
     private final List<NamedExpression> outputExpressions;
+    private final boolean withInProjection;
 
     /**
      * Desc: Constructor for LogicalRepeat.
@@ -59,7 +61,7 @@ public class LogicalRepeat<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_T
             List<List<Expression>> groupingSets,
             List<NamedExpression> outputExpressions,
             CHILD_TYPE child) {
-        this(groupingSets, outputExpressions, Optional.empty(), 
Optional.empty(), child);
+        this(groupingSets, outputExpressions, Optional.empty(), 
Optional.empty(), true, child);
     }
 
     /**
@@ -67,6 +69,7 @@ public class LogicalRepeat<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_T
      */
     public LogicalRepeat(List<List<Expression>> groupingSets, 
List<NamedExpression> outputExpressions,
             Optional<GroupExpression> groupExpression, 
Optional<LogicalProperties> logicalProperties,
+            boolean withInProjection,
             CHILD_TYPE child) {
         super(PlanType.LOGICAL_REPEAT, groupExpression, logicalProperties, 
child);
         this.groupingSets = Objects.requireNonNull(groupingSets, "groupingSets 
can not be null")
@@ -75,6 +78,7 @@ public class LogicalRepeat<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_T
                 .collect(ImmutableList.toImmutableList());
         this.outputExpressions = ImmutableList.copyOf(
                 Objects.requireNonNull(outputExpressions, "outputExpressions 
can not be null"));
+        this.withInProjection = withInProjection;
     }
 
     @Override
@@ -100,6 +104,36 @@ public class LogicalRepeat<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_T
         );
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        // org.apache.doris.nereids.parser.LogicalPlanBuilder.withProjection 
will generate different plan for
+        // distinct aggregation so use withInProjection flag to control 
whether to generate a select statement
+        // eg: select distinct log_time from example_tbl_duplicate group by 
log_time,log_type with rollup;
+        // select log_time from example_tbl_duplicate group by 
log_time,log_type with rollup;
+        if (!withInProjection) {
+            sb.append("SELECT ");
+            sb.append(
+                    outputExpressions.stream().map(Expression::toDigest)
+                            .collect(Collectors.joining(", "))
+            );
+            sb.append(" FROM ");
+        }
+        sb.append(child().toDigest());
+        sb.append(" GROUP BY GROUPING SETS (");
+        for (int i = 0; i < groupingSets.size(); i++) {
+            List<Expression> groupingSet = groupingSets.get(i);
+            String subSet = groupingSet.stream().map(Expression::toDigest)
+                    .collect(Collectors.joining(",", "(", ")"));
+            sb.append(subSet);
+            if (i != groupingSets.size() - 1) {
+                sb.append(", ");
+            }
+        }
+        sb.append(")");
+        return sb.toString();
+    }
+
     @Override
     public List<Slot> computeOutput() {
         return outputExpressions.stream()
@@ -146,7 +180,7 @@ public class LogicalRepeat<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_T
     @Override
     public LogicalRepeat<CHILD_TYPE> 
withGroupExpression(Optional<GroupExpression> groupExpression) {
         return new LogicalRepeat<>(groupingSets, outputExpressions, 
groupExpression,
-                Optional.of(getLogicalProperties()), child());
+                Optional.of(getLogicalProperties()), withInProjection, 
child());
     }
 
     @Override
@@ -154,7 +188,7 @@ public class LogicalRepeat<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_T
             Optional<LogicalProperties> logicalProperties, List<Plan> 
children) {
         Preconditions.checkArgument(children.size() == 1);
         return new LogicalRepeat<>(groupingSets, outputExpressions, 
groupExpression, logicalProperties,
-                children.get(0));
+                withInProjection, children.get(0));
     }
 
     public LogicalRepeat<CHILD_TYPE> withGroupSets(List<List<Expression>> 
groupingSets) {
@@ -180,6 +214,11 @@ public class LogicalRepeat<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_T
         return new LogicalRepeat<>(groupingSets, newOutput, child);
     }
 
+    public LogicalRepeat<CHILD_TYPE> withInProjection(boolean 
withInProjection) {
+        return new LogicalRepeat<>(groupingSets, outputExpressions,
+                Optional.empty(), Optional.empty(), withInProjection, child());
+    }
+
     public boolean canBindVirtualSlot() {
         return bound() && outputExpressions.stream()
                 .noneMatch(output -> 
output.containsType(VirtualSlotReference.class));
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSelectHint.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSelectHint.java
index 10b67a84f4e..2257444cc1f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSelectHint.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSelectHint.java
@@ -127,4 +127,9 @@ public class LogicalSelectHint<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHI
                 .collect(Collectors.joining(", "));
         return "LogicalSelectHint (" + hintStr + ")";
     }
+
+    @Override
+    public String toDigest() {
+        return child().toDigest();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSort.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSort.java
index 607fcf25bca..45e0e081a3c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSort.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSort.java
@@ -37,6 +37,7 @@ import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 /**
  * Logical Sort plan.
@@ -89,6 +90,17 @@ public class LogicalSort<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYP
                 "orderKeys", orderKeys);
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(child().toDigest()).append(" ORDER BY ");
+        sb.append(
+                orderKeys.stream().map(OrderKey::toDigest)
+                        .collect(Collectors.joining(", "))
+        );
+        return sb.toString();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSubQueryAlias.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSubQueryAlias.java
index 328508fb3c7..2f49dc1ce85 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSubQueryAlias.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalSubQueryAlias.java
@@ -42,6 +42,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * The node of logical plan for sub query and alias
@@ -138,6 +139,18 @@ public class LogicalSubQueryAlias<CHILD_TYPE extends Plan> 
extends LogicalUnary<
         ));
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("(").append(child().toDigest()).append(") AS ");
+        sb.append(qualifier.get(0));
+        if (columnAliases.isPresent()) {
+            columnAliases.get().stream()
+                    .collect(Collectors.joining(", ", "(", ")"));
+        }
+        return sb.toString();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalUnion.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalUnion.java
index ebad64dc16e..1e563d5676d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalUnion.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalUnion.java
@@ -123,6 +123,15 @@ public class LogicalUnion extends LogicalSetOperation 
implements Union, OutputPr
                 "stats", statistics);
     }
 
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("(").append(child(0).toDigest()).append(")");
+        sb.append(" UNION ").append(qualifier).append(" ");
+        sb.append("(").append(child(1).toDigest()).append(")");
+        return sb.toString();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalUsingJoin.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalUsingJoin.java
index 78331438a84..0b056da686c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalUsingJoin.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalUsingJoin.java
@@ -38,6 +38,7 @@ import com.google.common.collect.Lists;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * select col1 from t1 join t2 using(col1);
@@ -147,4 +148,19 @@ public class LogicalUsingJoin<LEFT_CHILD_TYPE extends 
Plan, RIGHT_CHILD_TYPE ext
         }
         return Utils.toSqlStringSkipNull("UsingJoin[" + id.asInt() + "]", 
args.toArray());
     }
+
+    @Override
+    public String toDigest() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(left().toDigest());
+        sb.append(" ").append(joinType).append(" ");
+        sb.append(right().toDigest());
+        sb.append(" USING (");
+        sb.append(
+                usingSlots.stream().map(Expression::toDigest)
+                        .collect(Collectors.joining(", "))
+        );
+        sb.append(")");
+        return sb.toString();
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserDigestTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserDigestTest.java
new file mode 100644
index 00000000000..ddbdcbdf0c4
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserDigestTest.java
@@ -0,0 +1,300 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.parser;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.Pair;
+import org.apache.doris.nereids.StatementContext;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.qe.ConnectContext;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class NereidsParserDigestTest extends ParserTestBase {
+
+    private void assertDigestEquals(String expected,
+            List<Pair<LogicalPlan, StatementContext>> logicalPlanList) {
+        String digest = logicalPlanList.get(0).first.toDigest();
+        Assertions.assertEquals(expected, digest);
+    }
+
+    @Test
+    public void testDigest() {
+        NereidsParser nereidsParser = new NereidsParser();
+        // test simple query
+        String sql
+                = "SELECT (a+1) as b, c as d, abs(f) as f FROM test where not 
exists(select d from test2) "
+                + "and c in (1,2,3) and e in (select e from test3) and d is 
null "
+                + "and f = (select f from testf) and e = [1, 3, 5];";
+        List<Pair<LogicalPlan, StatementContext>> logicalPlanList = 
nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT a + ? AS b, c AS d, ABS(f) AS f FROM test 
WHERE "
+                        + "(NOT EXISTS (SELECT d FROM test2) AND c IN (?) AND 
e IN (SELECT e FROM test3) "
+                        + "AND d IS NULL AND f = (SELECT f FROM testf) AND e = 
[?])",
+                logicalPlanList);
+
+        // test group by and order by
+        sql = "select a,b from test_table group by 1,2 order by 1";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT a, b FROM test_table GROUP BY 1, 2 ORDER BY 
1 ASC NULLS FIRST",
+                logicalPlanList);
+
+        // test explain
+        sql = "explain select a from test";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("EXPLAIN SELECT a FROM test", logicalPlanList);
+
+        // test variable
+        sql = "SELECT @@session.auto_increment_increment AS 
auto_increment_increment, @query_timeout AS query_timeout;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals(
+                "SELECT @@auto_increment_increment AS 
auto_increment_increment, @query_timeout AS query_timeout",
+                logicalPlanList);
+
+        // test one row relation
+        sql = "select 100, 'value'";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT ?, ?", logicalPlanList);
+
+        // test select tablet
+        sql = "select * from test tablet(1024)";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT * FROM test TABLET(?)", logicalPlanList);
+
+        // test except
+        sql = "select * except(age) from student;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT * EXCEPT(age) FROM student", 
logicalPlanList);
+
+        // test lateral view
+        sql = "SELECT * FROM person LATERAL VIEW EXPLODE(ARRAY(30, 60)) 
tableName AS c_age;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT * FROM person LATERAL VIEW EXPLODE(ARRAY(?, 
?)) tableName AS c_age",
+                logicalPlanList);
+
+        // test lambda
+        sql = "SELECT ARRAY_MAP(x->x+1, ARRAY(87, 33, -49))";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT ARRAY_MAP(x -> x + ?, ARRAY(?, ?, ?)) "
+                + "AS ARRAY_MAP(x->x+1, ARRAY(87, 33, -49))", logicalPlanList);
+
+        // test set operation
+        sql = "SELECT student_id, name\n"
+                + "FROM students\n"
+                + "EXCEPT\n"
+                + "SELECT student_id, name\n"
+                + "FROM graduated_students;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("(SELECT student_id, name FROM students) "
+                + "EXCEPT DISTINCT (SELECT student_id, name FROM 
graduated_students)", logicalPlanList);
+
+        sql = "SELECT student_id, name\n"
+                + "FROM math_students\n"
+                + "INTERSECT\n"
+                + "SELECT student_id, name\n"
+                + "FROM physics_students;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("(SELECT student_id, name FROM math_students) "
+                + "INTERSECT DISTINCT (SELECT student_id, name FROM 
physics_students)", logicalPlanList);
+
+        sql = "SELECT student_id, name, age\n"
+                + "FROM class1_students\n"
+                + "UNION\n"
+                + "SELECT student_id, name, age\n"
+                + "FROM class2_students;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("(SELECT student_id, name, age FROM 
class1_students) "
+                + "UNION DISTINCT (SELECT student_id, name, age FROM 
class2_students)", logicalPlanList);
+
+        // test tvf
+        sql = "SELECT cast(id as INT) as id, name, cast (age as INT) as age 
FROM "
+                + "s3(\"s3.access_key\"= \"ak\", \"s3.secret_key\" = \"sk\");";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT cast(id as INT) AS id, name, cast(age as 
INT) AS age FROM s3(?)", logicalPlanList);
+
+        // test filter + subquery + in list
+        sql = "select id, concat(firstname, ' ', lastname) as fullname from 
student where age in (18,20,25) and "
+                + "name like('_h%') and id in (select id from application) 
order by id desc limit 1,3;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT id, CONCAT(firstname, ?, lastname) AS 
fullname FROM student "
+                        + "WHERE (age IN (?) AND name like ? AND id IN (SELECT 
id FROM application)) "
+                        + "ORDER BY id DESC LIMIT ?  OFFSET ?",
+                logicalPlanList);
+
+        // test agg
+        sql = "select sum(price) as total,type, count(1) as t_count from 
tb_book where level > 1 group by type;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals(
+                "SELECT SUM(price) AS total, type, COUNT(?) AS t_count FROM 
tb_book WHERE level > ? GROUP BY type",
+                logicalPlanList);
+
+        // test distinct agg
+        sql = "select distinct type, id from tb_book group by type;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        // NOTE: this is special for distinct group by
+        assertDigestEquals("SELECT DISTINCT * FROM tb_book GROUP BY type", 
logicalPlanList);
+
+        sql = "select type, id from tb_book group by type;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT type, id FROM tb_book GROUP BY type", 
logicalPlanList);
+
+        // test distinct
+        sql = "select distinct type, id from tb_book";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT DISTINCT type, id FROM tb_book", 
logicalPlanList);
+
+        // test case when
+        sql = "select d_week_seq,\n"
+                + "        sum(case when (d_day_name='Sunday') then 
sales_price else null end) sun_sales,\n"
+                + "        sum(case when (d_day_name='Monday') then 
sales_price else null end) mon_sales,\n"
+                + "        sum(case when (d_day_name='Tuesday') then 
sales_price else  null end) tue_sales,\n"
+                + "        sum(case when (d_day_name='Wednesday') then 
sales_price else null end) wed_sales,\n"
+                + "        sum(case when (d_day_name='Thursday') then 
sales_price else null end) thu_sales,\n"
+                + "        sum(case when (d_day_name='Friday') then 
sales_price else null end) fri_sales,\n"
+                + "        sum(case when (d_day_name='Saturday') then 
sales_price else null end) sat_sales\n"
+                + " from wscs\n"
+                + "     ,date_dim\n"
+                + " where d_date_sk = sold_date_sk\n"
+                + " group by d_week_seq";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals(
+                "SELECT d_week_seq, "
+                        + "SUM(CASE WHEN d_day_name = ? THEN sales_price ELSE 
? END) AS sun_sales, "
+                        + "SUM(CASE WHEN d_day_name = ? THEN sales_price ELSE 
? END) AS mon_sales, "
+                        + "SUM(CASE WHEN d_day_name = ? THEN sales_price ELSE 
? END) AS tue_sales, "
+                        + "SUM(CASE WHEN d_day_name = ? THEN sales_price ELSE 
? END) AS wed_sales, "
+                        + "SUM(CASE WHEN d_day_name = ? THEN sales_price ELSE 
? END) AS thu_sales, "
+                        + "SUM(CASE WHEN d_day_name = ? THEN sales_price ELSE 
? END) AS fri_sales, "
+                        + "SUM(CASE WHEN d_day_name = ? THEN sales_price ELSE 
? END) AS sat_sales "
+                        + "FROM wscs CROSS_JOIN date_dim WHERE d_date_sk = 
sold_date_sk GROUP BY d_week_seq",
+                logicalPlanList);
+
+        // test cte
+        sql = "WITH\n"
+                + "  cte1 AS (SELECT a,b FROM table1),\n"
+                + "  cte2 AS (SELECT c,d FROM table2)\n"
+                + "SELECT b,d FROM cte1 JOIN cte2\n"
+                + "WHERE cte1.a = cte2.c;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("WITH\n"
+                + "(SELECT a,b FROM table1) AS cte1, (SELECT c,d FROM table2) 
AS cte2\n"
+                + "SELECT b,d FROM cte1 CROSS_JOIN cte2 WHERE cte1.a = 
cte2.c", logicalPlanList);
+
+        // test hint
+        sql = "select /*+ leading(t1 {t2 t3}) */ * from t1 left join t2 on c1 
= c2 join t3 on c2 = c3;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT * FROM t1 LEFT_OUTER_JOIN t2 ON c1 = c2 
INNER_JOIN t3 ON c2 = c3", logicalPlanList);
+
+        // test using join
+        sql = "SELECT order_id, name, order_date\n"
+                + "FROM orders\n"
+                + "JOIN customers\n"
+                + "USING (customer_id);";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT order_id, name, order_date FROM orders "
+                + "INNER_JOIN customers USING (customer_id)", logicalPlanList);
+
+        // test window function
+        sql = "select k1, sum(k2), rank() over(partition by k1 order by k1) as 
ranking from t1 group by k1";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals(
+                "SELECT k1, SUM(k2) AS sum(k2), RANK() OVER (PARTITION BY k1 
ORDER BY k1 ASC NULLS FIRST) AS ranking FROM t1 GROUP BY k1",
+                logicalPlanList);
+
+        // test binary keyword
+        sql = "SELECT BINARY 'abc' FROM t";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT ? FROM t", logicalPlanList);
+
+        // test rollup
+        sql = "SELECT a, b, sum(c) from test group by a ASC, b ASC WITH 
ROLLUP";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals(
+                "SELECT a, b, SUM(c) AS sum(c) FROM test GROUP BY GROUPING 
SETS ((a,b), (a), ()) ORDER BY a ASC NULLS FIRST, b ASC NULLS FIRST",
+                logicalPlanList);
+        sql = "SELECT a, b from test group by a, b WITH ROLLUP";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT a, b FROM test GROUP BY GROUPING SETS 
((a,b), (a), ())", logicalPlanList);
+        sql = "SELECT distinct a from test group by a, b WITH ROLLUP";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT DISTINCT * FROM test GROUP BY GROUPING SETS 
((a,b), (a), ())", logicalPlanList);
+
+        // test qualify
+        sql = "select country, sum(profit) as total, row_number() over (order 
by country) as rk from sales "
+                + "where year >= 2000 group by country having sum(profit) > 
100 qualify rk = 1";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT country, SUM(profit) AS total, "
+                        + "ROW_NUMBER() OVER (ORDER BY country ASC NULLS 
FIRST) AS rk "
+                        + "FROM sales WHERE year >= ? GROUP BY country HAVING 
SUM(profit) > ? QUALIFY rk = ?",
+                logicalPlanList);
+
+        sql = "select country, sum(profit) as total from sales where year >= 
2000 group by country "
+                + "having sum(profit) > 100 qualify row_number() over (order 
by country) = 1";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("SELECT country, SUM(profit) AS total FROM sales 
WHERE year >= ? GROUP BY country "
+                        + "HAVING SUM(profit) > ? QUALIFY ROW_NUMBER() OVER 
(ORDER BY country ASC NULLS FIRST) = ?",
+                logicalPlanList);
+
+        // test export
+        sql = "EXPORT TABLE test\n"
+                + "WHERE k1 < 50\n"
+                + "TO \"s3://bucket/export\"\n"
+                + "PROPERTIES (\n"
+                + "    \"columns\" = \"k1,k2\",\n"
+                + "    \"column_separator\"=\",\"\n"
+                + ") WITH s3 (\n"
+                + "    \"s3.endpoint\" = \"xxxxx\",\n"
+                + "    \"s3.region\" = \"xxxxx\"\n"
+                + ")";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("EXPORT TABLE test WHERE k1 < ? TO ?", 
logicalPlanList);
+
+        // test insert
+        ConnectContext ctx = ConnectContext.get();
+        ctx.setCurrentUserIdentity(UserIdentity.ROOT);
+        ctx.setRemoteIP("127.0.0.1");
+        ctx.setEnv(Env.getCurrentEnv());
+        ctx.setDatabase("mysql");
+
+        ctx.setThreadLocalInfo();
+        sql = "INSERT INTO test VALUES (1, 2);";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("INSERT INTO test VALUES ?", logicalPlanList);
+
+        sql = "INSERT INTO test (c1, c2) VALUES (1, 2), (3, 2 * 2);";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("INSERT INTO test (c1, c2) VALUES ?", 
logicalPlanList);
+
+        sql = "INSERT INTO test PARTITION(p1, p2) WITH LABEL `label1` SELECT * 
FROM test2;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        assertDigestEquals("INSERT INTO test SELECT * FROM test2", 
logicalPlanList);
+
+        sql = "INSERT OVERWRITE table test (c1, c2) VALUES (1, 2), (3, 2 * 
2);";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        // special for insert overwrite
+        assertDigestEquals("OVERWRITE TABLE INSERT INTO test (c1, c2) VALUES 
?", logicalPlanList);
+
+        sql = "INSERT OVERWRITE table test (c1, c2) SELECT * from test2;";
+        logicalPlanList = nereidsParser.parseMultiple(sql);
+        // special for insert overwrite
+        assertDigestEquals("OVERWRITE TABLE INSERT INTO test (c1, c2) SELECT * 
FROM test2", logicalPlanList);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to