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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2b375f66e0c [feature](Nereids): add functional dependencies  (#27051)
2b375f66e0c is described below

commit 2b375f66e0c57eb32bb7923ca3c514bedba56e07
Author: 谢健 <[email protected]>
AuthorDate: Wed Nov 29 12:46:25 2023 +0800

    [feature](Nereids): add functional dependencies  (#27051)
    
    Add functional dependencies: including
    
    - uniform slot, which means ndv <= 0
    - unique slot, which means ndv = row cound
---
 .../nereids/analyzer/UnboundOlapTableSink.java     |   4 +-
 .../nereids/analyzer/UnboundOneRowRelation.java    |   4 +-
 .../doris/nereids/analyzer/UnboundRelation.java    |   3 +-
 .../doris/nereids/analyzer/UnboundResultSink.java  |   4 +-
 .../doris/nereids/analyzer/UnboundTVFRelation.java |   4 +-
 .../hypergraph/receiver/PlanReceiver.java          |   7 +-
 .../nereids/properties/FunctionalDependencies.java | 232 ++++++++++++
 .../nereids/properties/LogicalProperties.java      |  15 +-
 .../properties/UnboundLogicalProperties.java       |   2 +-
 .../doris/nereids/rules/rewrite/MultiJoin.java     |   3 +-
 .../doris/nereids/trees/plans/AbstractPlan.java    |   8 +-
 .../plans/BlockFuncDepsPropagation.java}           |  33 +-
 .../apache/doris/nereids/trees/plans/FakePlan.java |   3 +-
 .../doris/nereids/trees/plans/GroupPlan.java       |   2 +-
 .../apache/doris/nereids/trees/plans/JoinType.java |   8 +
 .../nereids/trees/plans/PropagateFuncDeps.java     |  44 +++
 .../nereids/trees/plans/commands/Command.java      |   3 +-
 .../trees/plans/logical/LogicalAggregate.java      |  81 +++++
 .../nereids/trees/plans/logical/LogicalApply.java  |   3 +-
 .../trees/plans/logical/LogicalAssertNumRows.java  |   4 +-
 .../nereids/trees/plans/logical/LogicalCTE.java    |   3 +-
 .../trees/plans/logical/LogicalCTEAnchor.java      |   4 +-
 .../trees/plans/logical/LogicalCTEConsumer.java    |   4 +-
 .../trees/plans/logical/LogicalCTEProducer.java    |   4 +-
 .../plans/logical/LogicalCatalogRelation.java      |  19 +
 .../trees/plans/logical/LogicalCheckPolicy.java    |   4 +-
 .../logical/LogicalDeferMaterializeResultSink.java |   3 +-
 .../plans/logical/LogicalDeferMaterializeTopN.java |  19 +-
 .../trees/plans/logical/LogicalEmptyRelation.java  |   4 +-
 .../nereids/trees/plans/logical/LogicalExcept.java |  25 ++
 .../trees/plans/logical/LogicalFileSink.java       |   4 +-
 .../nereids/trees/plans/logical/LogicalFilter.java |  12 +
 .../trees/plans/logical/LogicalGenerate.java       |   9 +
 .../nereids/trees/plans/logical/LogicalHaving.java |  12 +
 .../trees/plans/logical/LogicalIntersect.java      |  29 ++
 .../nereids/trees/plans/logical/LogicalJoin.java   |  96 ++++-
 .../nereids/trees/plans/logical/LogicalLimit.java  |  15 +
 .../trees/plans/logical/LogicalOlapTableSink.java  |   4 +-
 .../trees/plans/logical/LogicalOneRowRelation.java |  12 +
 .../trees/plans/logical/LogicalPartitionTopN.java  |   4 +-
 .../nereids/trees/plans/logical/LogicalPlan.java   |  10 +
 .../trees/plans/logical/LogicalProject.java        |  28 ++
 .../nereids/trees/plans/logical/LogicalRepeat.java |  12 +
 .../trees/plans/logical/LogicalResultSink.java     |   4 +-
 .../trees/plans/logical/LogicalSelectHint.java     |   4 +-
 .../nereids/trees/plans/logical/LogicalSort.java   |   4 +-
 .../trees/plans/logical/LogicalSubQueryAlias.java  |  17 +
 .../trees/plans/logical/LogicalTVFRelation.java    |   3 +-
 .../nereids/trees/plans/logical/LogicalTopN.java   |  16 +
 .../nereids/trees/plans/logical/LogicalUnion.java  |  14 +
 .../nereids/trees/plans/logical/LogicalWindow.java |  45 +++
 .../nereids/trees/plans/logical/UsingJoin.java     |   3 +-
 .../apache/doris/nereids/util/ExpressionUtils.java |  31 ++
 .../translator/PhysicalPlanTranslatorTest.java     |   3 +-
 .../nereids/jobs/cascades/DeriveStatsJobTest.java  |   3 +-
 .../org/apache/doris/nereids/memo/MemoTest.java    |  11 +-
 .../postprocess/MergeProjectPostProcessTest.java   |   3 +-
 .../PushDownFilterThroughProjectTest.java          |   3 +-
 .../properties/FunctionalDependenciesTest.java     | 387 +++++++++++++++++++++
 .../doris/nereids/stats/JoinEstimateTest.java      |   9 +-
 .../doris/nereids/stats/StatsCalculatorTest.java   |   6 +-
 61 files changed, 1294 insertions(+), 75 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundOlapTableSink.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundOlapTableSink.java
index 1fd371d8568..d9a039a8c8d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundOlapTableSink.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundOlapTableSink.java
@@ -23,6 +23,7 @@ import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.properties.UnboundLogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.algebra.Sink;
@@ -40,7 +41,8 @@ import java.util.Optional;
 /**
  * Represent an olap table sink plan node that has not been bound.
  */
-public class UnboundOlapTableSink<CHILD_TYPE extends Plan> extends 
LogicalSink<CHILD_TYPE> implements Unbound, Sink {
+public class UnboundOlapTableSink<CHILD_TYPE extends Plan> extends 
LogicalSink<CHILD_TYPE>
+        implements Unbound, Sink, BlockFuncDepsPropagation {
 
     private final List<String> nameParts;
     private final List<String> colNames;
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 d2574c6ff2a..160b7210f7a 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
@@ -24,6 +24,7 @@ import 
org.apache.doris.nereids.properties.UnboundLogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.RelationId;
@@ -41,7 +42,8 @@ import java.util.Optional;
  * A relation that contains only one row consist of some constant expressions.
  * e.g. select 100, 'value'
  */
-public class UnboundOneRowRelation extends LogicalRelation implements Unbound, 
OneRowRelation {
+public class UnboundOneRowRelation extends LogicalRelation implements Unbound, 
OneRowRelation,
+        BlockFuncDepsPropagation {
 
     private final List<NamedExpression> projects;
 
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 dd0033e9e5d..080e749c05b 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
@@ -24,6 +24,7 @@ import 
org.apache.doris.nereids.properties.UnboundLogicalProperties;
 import org.apache.doris.nereids.trees.TableSample;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.RelationId;
@@ -43,7 +44,7 @@ import java.util.Optional;
 /**
  * Represent a relation plan node that has not been bound.
  */
-public class UnboundRelation extends LogicalRelation implements Unbound {
+public class UnboundRelation extends LogicalRelation implements Unbound, 
BlockFuncDepsPropagation {
 
     private final List<String> nameParts;
     private final List<String> partNames;
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 99a3a591f2b..6f378e99112 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
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.memo.GroupExpression;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.algebra.Sink;
@@ -38,7 +39,8 @@ import java.util.Optional;
 /**
  * unbound result sink
  */
-public class UnboundResultSink<CHILD_TYPE extends Plan> extends 
LogicalSink<CHILD_TYPE> implements Unbound, Sink {
+public class UnboundResultSink<CHILD_TYPE extends Plan> extends 
LogicalSink<CHILD_TYPE>
+        implements Unbound, Sink, BlockFuncDepsPropagation {
 
     public UnboundResultSink(CHILD_TYPE child) {
         super(PlanType.LOGICAL_UNBOUND_RESULT_SINK, ImmutableList.of(), child);
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 51ffb722b77..13b8336933a 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
@@ -25,6 +25,7 @@ import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Properties;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import 
org.apache.doris.nereids.trees.expressions.functions.table.TableValuedFunction;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.RelationId;
@@ -38,7 +39,8 @@ import java.util.Objects;
 import java.util.Optional;
 
 /** UnboundTVFRelation */
-public class UnboundTVFRelation extends LogicalRelation implements 
TVFRelation, Unbound {
+public class UnboundTVFRelation extends LogicalRelation implements 
TVFRelation, Unbound,
+        BlockFuncDepsPropagation {
 
     private final String functionName;
     private final Properties properties;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/receiver/PlanReceiver.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/receiver/PlanReceiver.java
index 31a6786ac3e..2eb386ce21c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/receiver/PlanReceiver.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/receiver/PlanReceiver.java
@@ -27,6 +27,7 @@ import org.apache.doris.nereids.memo.CopyInResult;
 import org.apache.doris.nereids.memo.Group;
 import org.apache.doris.nereids.memo.GroupExpression;
 import org.apache.doris.nereids.memo.Memo;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.properties.PhysicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
@@ -214,7 +215,7 @@ public class PlanReceiver implements AbstractReceiver {
             List<Expression> otherConjuncts) {
         // Check whether only NSL can be performed
         LogicalProperties joinProperties = new LogicalProperties(
-                () -> JoinUtils.getJoinOutput(joinType, left, right));
+                () -> JoinUtils.getJoinOutput(joinType, left, right), () -> 
FunctionalDependencies.EMPTY_FUNC_DEPS);
         List<Plan> plans = Lists.newArrayList();
         if (JoinUtils.shouldNestedLoopJoin(joinType, hashConjuncts)) {
             plans.add(new PhysicalNestedLoopJoin<>(joinType, hashConjuncts, 
otherConjuncts,
@@ -378,7 +379,9 @@ public class PlanReceiver implements AbstractReceiver {
                 .collect(Collectors.toList());
         if (!outputSet.equals(new HashSet<>(projects))) {
             LogicalProperties projectProperties = new LogicalProperties(
-                    () -> projects.stream().map(p -> 
p.toSlot()).collect(Collectors.toList()));
+                    () -> projects.stream()
+                            .map(p -> p.toSlot())
+                            .collect(Collectors.toList()), () -> 
FunctionalDependencies.EMPTY_FUNC_DEPS);
             allChild = allChild.stream()
                     .map(c -> new PhysicalProject<>(projects, 
projectProperties, c))
                     .collect(Collectors.toList());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/FunctionalDependencies.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/FunctionalDependencies.java
new file mode 100644
index 00000000000..73a78b0da25
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/FunctionalDependencies.java
@@ -0,0 +1,232 @@
+// 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.properties;
+
+import org.apache.doris.nereids.trees.expressions.Slot;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Record functional dependencies, aka func deps, including
+ * 1. unique slot: it means the column that has ndv = row count, can be null
+ * 2. uniform slotL it means the column that has ndv = 1, can be null
+ */
+public class FunctionalDependencies {
+    public static final FunctionalDependencies EMPTY_FUNC_DEPS
+            = new FunctionalDependencies(new NestedSet().toImmutable(), new 
NestedSet().toImmutable());
+    private final NestedSet uniqueSet;
+    private final NestedSet uniformSet;
+
+    private FunctionalDependencies(NestedSet uniqueSet, NestedSet uniformSet) {
+        this.uniqueSet = uniqueSet;
+        this.uniformSet = uniformSet;
+    }
+
+    public boolean isEmpty() {
+        return uniformSet.isEmpty() && uniqueSet.isEmpty();
+    }
+
+    public boolean isUnique(Slot slot) {
+        return uniqueSet.contains(slot);
+    }
+
+    public boolean isUnique(Set<Slot> slotSet) {
+        return !slotSet.isEmpty() && uniqueSet.containsAnySub(slotSet);
+    }
+
+    public boolean isUniform(Slot slot) {
+        return uniformSet.contains(slot);
+    }
+
+    public boolean isUniform(ImmutableSet<Slot> slotSet) {
+        return !slotSet.isEmpty()
+                && (uniformSet.contains(slotSet) || 
slotSet.stream().allMatch(uniformSet::contains));
+    }
+
+    public boolean isUniqueAndNotNull(Slot slot) {
+        return !slot.nullable() && isUnique(slot);
+    }
+
+    public boolean isUniqueAndNotNull(Set<Slot> slotSet) {
+        return slotSet.stream().noneMatch(Slot::nullable) && isUnique(slotSet);
+    }
+
+    public boolean isUniformAndNotNull(Slot slot) {
+        return !slot.nullable() && isUniform(slot);
+    }
+
+    public boolean isUniformAndNotNull(ImmutableSet<Slot> slotSet) {
+        return slotSet.stream().noneMatch(Slot::nullable) && 
isUniform(slotSet);
+    }
+
+    @Override
+    public String toString() {
+        return "uniform:" + uniformSet
+                + "\t unique:" + uniqueSet;
+    }
+
+    /**
+     * Builder of Func Deps
+     */
+    public static class Builder {
+        private final NestedSet uniqueSet;
+        private final NestedSet uniformSet;
+
+        public Builder() {
+            uniqueSet = new NestedSet();
+            uniformSet = new NestedSet();
+        }
+
+        public Builder(FunctionalDependencies other) {
+            this.uniformSet = new NestedSet(other.uniformSet);
+            this.uniqueSet = new NestedSet(other.uniqueSet);
+        }
+
+        public void addUniformSlot(Slot slot) {
+            uniformSet.add(slot);
+        }
+
+        public void addUniformSlot(FunctionalDependencies 
functionalDependencies) {
+            uniformSet.add(functionalDependencies.uniformSet);
+        }
+
+        public void addUniformSlot(ImmutableSet<Slot> slotSet) {
+            uniformSet.add(slotSet);
+        }
+
+        public void addUniqueSlot(Slot slot) {
+            uniqueSet.add(slot);
+        }
+
+        public void addUniqueSlot(ImmutableSet<Slot> slotSet) {
+            uniqueSet.add(slotSet);
+        }
+
+        public void addFunctionalDependencies(FunctionalDependencies fd) {
+            uniformSet.add(fd.uniformSet);
+            uniqueSet.add(fd.uniqueSet);
+        }
+
+        public FunctionalDependencies build() {
+            return new FunctionalDependencies(uniqueSet.toImmutable(), 
uniformSet.toImmutable());
+        }
+
+        public void pruneSlots(Set<Slot> outputSlots) {
+            uniformSet.removeNotContain(outputSlots);
+            uniqueSet.removeNotContain(outputSlots);
+        }
+
+        public void replace(Map<Slot, Slot> replaceMap) {
+            uniformSet.replace(replaceMap);
+            uniqueSet.replace(replaceMap);
+        }
+    }
+
+    static class NestedSet {
+        Set<Slot> slots;
+        Set<ImmutableSet<Slot>> slotSets;
+
+        NestedSet() {
+            slots = new HashSet<>();
+            slotSets = new HashSet<>();
+        }
+
+        NestedSet(NestedSet o) {
+            this.slots = new HashSet<>(o.slots);
+            this.slotSets = new HashSet<>(o.slotSets);
+        }
+
+        NestedSet(Set<Slot> slots, Set<ImmutableSet<Slot>> slotSets) {
+            this.slots = slots;
+            this.slotSets = slotSets;
+        }
+
+        public boolean contains(Slot slot) {
+            return slots.contains(slot);
+        }
+
+        public boolean contains(ImmutableSet<Slot> slotSet) {
+            if (slotSet.size() == 1) {
+                return slots.contains(slotSet.iterator().next());
+            }
+            return slotSets.contains(slotSet);
+        }
+
+        public boolean containsAnySub(Set<Slot> slotSet) {
+            return slotSet.stream().anyMatch(s -> slots.contains(s))
+                    || slotSets.stream().anyMatch(slotSet::containsAll);
+        }
+
+        public void removeNotContain(Set<Slot> slotSet) {
+            slots = slots.stream()
+                    .filter(slotSet::contains)
+                    .collect(Collectors.toSet());
+            slotSets = slotSets.stream()
+                    .filter(slotSet::containsAll)
+                    .collect(Collectors.toSet());
+        }
+
+        public void add(Slot slot) {
+            slots.add(slot);
+        }
+
+        public void add(ImmutableSet<Slot> slotSet) {
+            if (slotSet.isEmpty()) {
+                return;
+            }
+            if (slotSet.size() == 1) {
+                slots.add(slotSet.iterator().next());
+                return;
+            }
+            slotSets.add(slotSet);
+        }
+
+        public void add(NestedSet nestedSet) {
+            slots.addAll(nestedSet.slots);
+            slotSets.addAll(nestedSet.slotSets);
+        }
+
+        public boolean isEmpty() {
+            return slots.isEmpty() && slotSets.isEmpty();
+        }
+
+        @Override
+        public String toString() {
+            return "{" + slots + slotSets + "}";
+        }
+
+        public void replace(Map<Slot, Slot> replaceMap) {
+            slots = slots.stream()
+                    .map(s -> replaceMap.getOrDefault(s, s))
+                    .collect(Collectors.toSet());
+            slotSets = slotSets.stream()
+                    .map(set -> 
set.stream().map(replaceMap::get).collect(ImmutableSet.toImmutableSet()))
+                    .collect(Collectors.toSet());
+        }
+
+        public NestedSet toImmutable() {
+            return new NestedSet(ImmutableSet.copyOf(slots), 
ImmutableSet.copyOf(slotSets));
+        }
+    }
+}
+
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/LogicalProperties.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/LogicalProperties.java
index e72d07e3097..f82b43eaea6 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/LogicalProperties.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/LogicalProperties.java
@@ -42,10 +42,11 @@ public class LogicalProperties {
     protected final Supplier<Set<Slot>> outputSetSupplier;
     protected final Supplier<Map<Slot, Slot>> outputMapSupplier;
     protected final Supplier<Set<ExprId>> outputExprIdSetSupplier;
+    protected final Supplier<FunctionalDependencies> fdSupplier;
     private Integer hashCode = null;
 
-    public LogicalProperties(Supplier<List<Slot>> outputSupplier) {
-        this(outputSupplier, ImmutableList::of);
+    public LogicalProperties(Supplier<List<Slot>> outputSupplier, 
Supplier<FunctionalDependencies> fdSupplier) {
+        this(outputSupplier, fdSupplier, ImmutableList::of);
     }
 
     /**
@@ -54,7 +55,8 @@ public class LogicalProperties {
      * @param outputSupplier provide the output. Supplier can lazy compute 
output without
      *                       throw exception for which children have 
UnboundRelation
      */
-    public LogicalProperties(Supplier<List<Slot>> outputSupplier, 
Supplier<List<Slot>> nonUserVisibleOutputSupplier) {
+    public LogicalProperties(Supplier<List<Slot>> outputSupplier,
+            Supplier<FunctionalDependencies> fdSupplier, Supplier<List<Slot>> 
nonUserVisibleOutputSupplier) {
         this.outputSupplier = Suppliers.memoize(
                 Objects.requireNonNull(outputSupplier, "outputSupplier can not 
be null")
         );
@@ -73,6 +75,9 @@ public class LogicalProperties {
                         .map(NamedExpression::getExprId)
                         .collect(ImmutableSet.toImmutableSet())
         );
+        this.fdSupplier = Suppliers.memoize(
+                Objects.requireNonNull(fdSupplier, "FunctionalDependencies can 
not be null")
+        );
     }
 
     public List<Slot> getOutput() {
@@ -91,6 +96,10 @@ public class LogicalProperties {
         return outputExprIdSetSupplier.get();
     }
 
+    public FunctionalDependencies getFunctionalDependencies() {
+        return fdSupplier.get();
+    }
+
     public List<Id> getOutputExprIds() {
         return outputExprIdsSupplier.get();
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/UnboundLogicalProperties.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/UnboundLogicalProperties.java
index a086e54e7a6..886b7aceb13 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/UnboundLogicalProperties.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/UnboundLogicalProperties.java
@@ -32,7 +32,7 @@ public class UnboundLogicalProperties extends 
LogicalProperties {
     public static final UnboundLogicalProperties INSTANCE = new 
UnboundLogicalProperties();
 
     private UnboundLogicalProperties() {
-        super(ImmutableList::of);
+        super(ImmutableList::of, () -> FunctionalDependencies.EMPTY_FUNC_DEPS);
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MultiJoin.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MultiJoin.java
index 6f25e90320a..83e849086ab 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MultiJoin.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MultiJoin.java
@@ -21,6 +21,7 @@ import org.apache.doris.nereids.memo.GroupExpression;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.JoinType;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
@@ -52,7 +53,7 @@ import java.util.Optional;
  * firstChild [RIGHT (OUTER/SEMI/ANTI) JOIN] children[1, last].
  * eg: MJ([ROJ] A, B, C, D) is {A} [ROJ] {B C D}.
  */
-public class MultiJoin extends AbstractLogicalPlan {
+public class MultiJoin extends AbstractLogicalPlan implements 
BlockFuncDepsPropagation {
     /*
      *        topJoin
      *        /     \            MultiJoin
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/AbstractPlan.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/AbstractPlan.java
index 1e4fbdcf41d..764091e3513 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/AbstractPlan.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/AbstractPlan.java
@@ -19,12 +19,14 @@ package org.apache.doris.nereids.trees.plans;
 
 import org.apache.doris.nereids.analyzer.Unbound;
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.properties.UnboundLogicalProperties;
 import org.apache.doris.nereids.trees.AbstractTreeNode;
 import org.apache.doris.nereids.trees.expressions.ExprId;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
 import org.apache.doris.nereids.util.MutableState;
 import org.apache.doris.nereids.util.MutableState.EmptyMutableState;
 import org.apache.doris.nereids.util.TreeStringUtils;
@@ -169,7 +171,11 @@ public abstract class AbstractPlan extends 
AbstractTreeNode<Plan> implements Pla
         if (hasUnboundChild || hasUnboundExpression()) {
             return UnboundLogicalProperties.INSTANCE;
         } else {
-            return new LogicalProperties(this::computeOutput);
+            Supplier<List<Slot>> outputSupplier = 
Suppliers.memoize(this::computeOutput);
+            Supplier<FunctionalDependencies> fdSupplier = () -> this 
instanceof LogicalPlan
+                    ? ((LogicalPlan) this).computeFuncDeps(outputSupplier)
+                    : FunctionalDependencies.EMPTY_FUNC_DEPS;
+            return new LogicalProperties(outputSupplier, fdSupplier);
         }
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/UnboundLogicalProperties.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/BlockFuncDepsPropagation.java
similarity index 52%
copy from 
fe/fe-core/src/main/java/org/apache/doris/nereids/properties/UnboundLogicalProperties.java
copy to 
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/BlockFuncDepsPropagation.java
index a086e54e7a6..3f60727576d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/UnboundLogicalProperties.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/BlockFuncDepsPropagation.java
@@ -15,38 +15,21 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package org.apache.doris.nereids.properties;
+package org.apache.doris.nereids.trees.plans;
 
-import org.apache.doris.nereids.exceptions.UnboundException;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.trees.expressions.Slot;
-
-import com.google.common.collect.ImmutableList;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
 
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
- * LogicalPlan must compute and return non-null LogicalProperties without 
exception,
- * so UnboundRelation.computeLogicalProperties() return a 
UnboundLogicalProperties temporary.
+ * Block fd propagation, it always returns an empty fd
  */
-public class UnboundLogicalProperties extends LogicalProperties {
-    public static final UnboundLogicalProperties INSTANCE = new 
UnboundLogicalProperties();
-
-    private UnboundLogicalProperties() {
-        super(ImmutableList::of);
-    }
-
-    @Override
-    public List<Slot> getOutput() {
-        throw new UnboundException("getOutput");
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        return o instanceof UnboundLogicalProperties;
-    }
-
+public interface BlockFuncDepsPropagation extends LogicalPlan {
     @Override
-    public int hashCode() {
-        return 0;
+    default FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        return FunctionalDependencies.EMPTY_FUNC_DEPS;
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/FakePlan.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/FakePlan.java
index b964bec2621..ad186c680ce 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/FakePlan.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/FakePlan.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.trees.plans;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
@@ -77,7 +78,7 @@ public class FakePlan implements Plan {
 
     @Override
     public LogicalProperties getLogicalProperties() {
-        return new LogicalProperties(ArrayList::new);
+        return new LogicalProperties(ArrayList::new, () -> 
FunctionalDependencies.EMPTY_FUNC_DEPS);
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/GroupPlan.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/GroupPlan.java
index 0c3f519038f..49056add395 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/GroupPlan.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/GroupPlan.java
@@ -37,7 +37,7 @@ import java.util.Optional;
  * Used in {@link 
org.apache.doris.nereids.pattern.GroupExpressionMatching.GroupExpressionIterator},
  * as a place-holder when do match root.
  */
-public class GroupPlan extends LogicalLeaf {
+public class GroupPlan extends LogicalLeaf implements BlockFuncDepsPropagation 
{
 
     private final Group group;
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/JoinType.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/JoinType.java
index 6470b184c89..6b4c063986c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/JoinType.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/JoinType.java
@@ -160,6 +160,10 @@ public enum JoinType {
         return this == LEFT_ANTI_JOIN;
     }
 
+    public final boolean isLefSemiJoin() {
+        return this == LEFT_SEMI_JOIN;
+    }
+
     public final boolean isRightSemiOrAntiJoin() {
         return this == RIGHT_SEMI_JOIN || this == RIGHT_ANTI_JOIN;
     }
@@ -168,6 +172,10 @@ public enum JoinType {
         return this == RIGHT_ANTI_JOIN;
     }
 
+    public final boolean isRightSemiJoin() {
+        return this == RIGHT_SEMI_JOIN;
+    }
+
     public final boolean isSemiOrAntiJoin() {
         return this == LEFT_SEMI_JOIN || this == RIGHT_SEMI_JOIN || this == 
LEFT_ANTI_JOIN
                 || this == NULL_AWARE_LEFT_ANTI_JOIN || this == 
RIGHT_ANTI_JOIN;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PropagateFuncDeps.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PropagateFuncDeps.java
new file mode 100644
index 00000000000..332a80c99ee
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PropagateFuncDeps.java
@@ -0,0 +1,44 @@
+// 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.trees.plans;
+
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Propagate fd, keep children's fd
+ */
+public interface PropagateFuncDeps extends LogicalPlan {
+    @Override
+    default FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        if (children().size() == 1) {
+            // Note when changing function dependencies, we always clone it.
+            // So it's safe to return a reference
+            return child(0).getLogicalProperties().getFunctionalDependencies();
+        }
+        FunctionalDependencies.Builder builder = new 
FunctionalDependencies.Builder();
+        children().stream()
+                .map(p -> p.getLogicalProperties().getFunctionalDependencies())
+                .forEach(builder::addFunctionalDependencies);
+        return builder.build();
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/Command.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/Command.java
index 2adc9d90429..b32d3bed44d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/Command.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/Command.java
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.AbstractPlan;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
@@ -36,7 +37,7 @@ import java.util.Optional;
 /**
  * All DDL and DML commands' super class.
  */
-public abstract class Command extends AbstractPlan implements LogicalPlan {
+public abstract class Command extends AbstractPlan implements LogicalPlan, 
BlockFuncDepsPropagation {
 
     protected Command(PlanType type) {
         super(type, Optional.empty(), Optional.empty(), null, 
ImmutableList.of());
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 072993f5f95..53877ff091e 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
@@ -18,23 +18,36 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.properties.FunctionalDependencies.Builder;
 import org.apache.doris.nereids.properties.LogicalProperties;
+import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.VirtualSlotReference;
 import org.apache.doris.nereids.trees.expressions.functions.ExpressionTrait;
+import 
org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
+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.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.algebra.Aggregate;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.nereids.util.ExpressionUtils;
 import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 /**
@@ -282,4 +295,72 @@ public class LogicalAggregate<CHILD_TYPE extends Plan>
         return new LogicalAggregate<>(normalizedGroupBy, normalizedOutput, 
true, ordinalIsResolved, generated,
                 hasPushed, sourceRepeat, Optional.empty(), Optional.empty(), 
normalizedChild);
     }
+
+    private void updateFuncDepsGroupByUnique(NamedExpression namedExpression, 
Builder fdBuilder) {
+        if (ExpressionUtils.isInjective(namedExpression)) {
+            
fdBuilder.addUniqueSlot(ImmutableSet.copyOf(namedExpression.getInputSlots()));
+            return;
+        }
+
+        if (!(namedExpression instanceof Alias && namedExpression.child(0) 
instanceof AggregateFunction)) {
+            return;
+        }
+
+        AggregateFunction agg = (AggregateFunction) namedExpression.child(0);
+        if (agg instanceof Count || agg instanceof Ndv) {
+            fdBuilder.addUniformSlot(namedExpression.toSlot());
+            return;
+        }
+
+        if (ExpressionUtils.isInjectiveAgg(agg)
+                && 
child().getLogicalProperties().getFunctionalDependencies().isUniqueAndNotNull(agg.getInputSlots()))
 {
+            fdBuilder.addUniqueSlot(namedExpression.toSlot());
+        }
+    }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies childFd = 
child(0).getLogicalProperties().getFunctionalDependencies();
+        Set<Slot> outputSet = new HashSet<>(outputSupplier.get());
+        Builder fdBuilder = new Builder();
+        // when group by all tuples, the result only have one row
+        if (groupByExpressions.isEmpty()) {
+            outputSet.forEach(s -> {
+                fdBuilder.addUniformSlot(s);
+                fdBuilder.addUniqueSlot(s);
+            });
+            return fdBuilder.build();
+        }
+
+        // when group by complicate expression or virtual slot, just propagate 
uniform slots
+        if (groupByExpressions.stream()
+                .anyMatch(s -> !(s instanceof SlotReference) || s instanceof 
VirtualSlotReference)) {
+            fdBuilder.addUniformSlot(childFd);
+            fdBuilder.pruneSlots(outputSet);
+            return fdBuilder.build();
+        }
+
+        // when group by uniform slot, the result only have one row
+        ImmutableSet<Slot> groupByKeys = groupByExpressions.stream()
+                .map(s -> (Slot) s)
+                .collect(ImmutableSet.toImmutableSet());
+        if (childFd.isUniformAndNotNull(groupByKeys)) {
+            outputSupplier.get().forEach(s -> {
+                fdBuilder.addUniformSlot(s);
+                fdBuilder.addUniqueSlot(s);
+            });
+        }
+
+        // when group by unique slot, the result depends on agg func
+        if (childFd.isUniqueAndNotNull(groupByKeys)) {
+            for (NamedExpression namedExpression : getOutputExpressions()) {
+                updateFuncDepsGroupByUnique(namedExpression, fdBuilder);
+            }
+        }
+
+        // group by keys is unique
+        fdBuilder.addUniqueSlot(groupByKeys);
+        fdBuilder.pruneSlots(outputSet);
+        return fdBuilder.build();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalApply.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalApply.java
index b4f25e9771f..f6cf13dfc76 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalApply.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalApply.java
@@ -28,6 +28,7 @@ import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SubqueryExpr;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
 
@@ -44,7 +45,7 @@ import java.util.Optional;
  * @param <RIGHT_CHILD_TYPE> subquery.
  */
 public class LogicalApply<LEFT_CHILD_TYPE extends Plan, RIGHT_CHILD_TYPE 
extends Plan>
-        extends LogicalBinary<LEFT_CHILD_TYPE, RIGHT_CHILD_TYPE> {
+        extends LogicalBinary<LEFT_CHILD_TYPE, RIGHT_CHILD_TYPE> implements 
PropagateFuncDeps {
 
     // correlation column
     private final List<Expression> correlationSlot;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalAssertNumRows.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalAssertNumRows.java
index 69b5942c72d..3ba0a29842a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalAssertNumRows.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalAssertNumRows.java
@@ -24,6 +24,7 @@ import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
 
@@ -40,7 +41,8 @@ import java.util.Optional;
  * If the number of rows is more than the desired num of rows, the query will 
be cancelled.
  * The cancelled reason will be reported by Backend and displayed back to the 
user.
  */
-public class LogicalAssertNumRows<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> {
+public class LogicalAssertNumRows<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> implements
+        PropagateFuncDeps {
 
     private final AssertNumRowsElement assertNumRowsElement;
 
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 e21726ad7c9..7a75dd5c1a4 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
@@ -23,6 +23,7 @@ import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
 
@@ -36,7 +37,7 @@ import java.util.Optional;
 /**
  * Logical Node for CTE
  */
-public class LogicalCTE<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> {
+public class LogicalCTE<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> implements PropagateFuncDeps {
 
     private final List<LogicalSubQueryAlias<Plan>> aliasQueries;
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEAnchor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEAnchor.java
index c7b6bb5158a..67aef52f979 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEAnchor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEAnchor.java
@@ -24,6 +24,7 @@ import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
 
@@ -36,7 +37,8 @@ import java.util.Optional;
  * LogicalCTEAnchor
  */
 public class LogicalCTEAnchor<LEFT_CHILD_TYPE extends Plan,
-        RIGHT_CHILD_TYPE extends Plan> extends LogicalBinary<LEFT_CHILD_TYPE, 
RIGHT_CHILD_TYPE> {
+        RIGHT_CHILD_TYPE extends Plan> extends LogicalBinary<LEFT_CHILD_TYPE, 
RIGHT_CHILD_TYPE> implements
+        PropagateFuncDeps {
 
     private final CTEId cteId;
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEConsumer.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEConsumer.java
index c96d71e5daa..97ea18600c3 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEConsumer.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEConsumer.java
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.CTEId;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.RelationId;
@@ -40,7 +41,8 @@ import java.util.Optional;
 /**
  * LogicalCTEConsumer
  */
-public class LogicalCTEConsumer extends LogicalRelation {
+//TODO: find cte producer and propagate its functional dependencies
+public class LogicalCTEConsumer extends LogicalRelation implements 
BlockFuncDepsPropagation {
 
     private final String name;
     private final CTEId cteId;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEProducer.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEProducer.java
index 64f2831d079..aeae13302d9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEProducer.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCTEProducer.java
@@ -24,6 +24,7 @@ import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
 
@@ -37,7 +38,8 @@ import java.util.Optional;
 /**
  * LogicalCTEProducer
  */
-public class LogicalCTEProducer<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> {
+public class LogicalCTEProducer<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> implements
+        PropagateFuncDeps {
 
     private final CTEId cteId;
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCatalogRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCatalogRelation.java
index 4dd5121ef83..a2c2c68ddc5 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCatalogRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCatalogRelation.java
@@ -19,10 +19,13 @@ package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.catalog.DatabaseIf;
 import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.TableIf;
 import org.apache.doris.datasource.CatalogIf;
 import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.properties.FunctionalDependencies.Builder;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
@@ -33,11 +36,13 @@ import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import org.apache.commons.lang3.StringUtils;
 
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * abstract class catalog relation for logical relation
@@ -113,4 +118,18 @@ public abstract class LogicalCatalogRelation extends 
LogicalRelation implements
         }
         return Utils.qualifiedName(qualifier, table.getName());
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        Builder fdBuilder = new Builder();
+        if (table instanceof OlapTable && ((OlapTable) 
table).getKeysType().isAggregationFamily()) {
+            ImmutableSet<Slot> slotSet = computeOutput().stream()
+                    .filter(SlotReference.class::isInstance)
+                    .filter(s -> ((SlotReference) s).getColumn().isPresent()
+                            && ((SlotReference) s).getColumn().get().isKey())
+                    .collect(ImmutableSet.toImmutableSet());
+            fdBuilder.addUniqueSlot(slotSet);
+        }
+        return fdBuilder.build();
+    }
 }
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 b9fc7ecfe66..12250bb7d76 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
@@ -27,6 +27,7 @@ import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.algebra.CatalogRelation;
 import org.apache.doris.nereids.trees.plans.commands.CreatePolicyCommand;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
@@ -46,7 +47,8 @@ import java.util.Optional;
 /**
  * Logical Check Policy
  */
-public class LogicalCheckPolicy<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> {
+public class LogicalCheckPolicy<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> implements
+        PropagateFuncDeps {
 
     public LogicalCheckPolicy(CHILD_TYPE child) {
         super(PlanType.LOGICAL_CHECK_POLICY, child);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalDeferMaterializeResultSink.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalDeferMaterializeResultSink.java
index 277d0dc903a..ab6efb8977a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalDeferMaterializeResultSink.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalDeferMaterializeResultSink.java
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.memo.GroupExpression;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.algebra.Sink;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
@@ -37,7 +38,7 @@ import java.util.Optional;
  * use for defer materialize top n
  */
 public class LogicalDeferMaterializeResultSink<CHILD_TYPE extends Plan>
-        extends LogicalSink<CHILD_TYPE> implements Sink {
+        extends LogicalSink<CHILD_TYPE> implements Sink, PropagateFuncDeps {
 
     private final LogicalResultSink<? extends Plan> logicalResultSink;
     private final OlapTable olapTable;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalDeferMaterializeTopN.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalDeferMaterializeTopN.java
index b775cb1db2f..6b6f6d432eb 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalDeferMaterializeTopN.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalDeferMaterializeTopN.java
@@ -18,6 +18,8 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.properties.FunctionalDependencies.Builder;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.properties.OrderKey;
 import org.apache.doris.nereids.trees.expressions.ExprId;
@@ -37,11 +39,13 @@ import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Supplier;
 
 /**
  * use for defer materialize top n
  */
-public class LogicalDeferMaterializeTopN<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> implements TopN {
+public class LogicalDeferMaterializeTopN<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE>
+        implements TopN {
 
     private final LogicalTopN<? extends Plan> logicalTopN;
 
@@ -111,6 +115,19 @@ public class LogicalDeferMaterializeTopN<CHILD_TYPE 
extends Plan> extends Logica
                 .collect(ImmutableList.toImmutableList());
     }
 
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies fd = 
child(0).getLogicalProperties().getFunctionalDependencies();
+        if (getLimit() == 1) {
+            Builder builder = new Builder();
+            List<Slot> output = outputSupplier.get();
+            output.forEach(builder::addUniformSlot);
+            output.forEach(builder::addUniqueSlot);
+            fd = builder.build();
+        }
+        return fd;
+    }
+
     @Override
     public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
         return visitor.visitLogicalDeferMaterializeTopN(this, context);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalEmptyRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalEmptyRelation.java
index 32e7e1144ac..2f6018fae99 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalEmptyRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalEmptyRelation.java
@@ -21,6 +21,7 @@ import org.apache.doris.nereids.memo.GroupExpression;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.RelationId;
@@ -39,7 +40,8 @@ import java.util.Optional;
  * e.g.
  * select * from tbl limit 0
  */
-public class LogicalEmptyRelation extends LogicalRelation implements 
EmptyRelation, OutputPrunable {
+public class LogicalEmptyRelation extends LogicalRelation
+        implements EmptyRelation, OutputPrunable, BlockFuncDepsPropagation {
 
     private final List<NamedExpression> projects;
 
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 f7e2c20fbfb..c1753f147e4 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
@@ -18,8 +18,10 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
@@ -27,9 +29,13 @@ import 
org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * Logical Except.
@@ -97,4 +103,23 @@ public class LogicalExcept extends LogicalSetOperation {
         return new LogicalExcept(qualifier, newOutputs, regularChildrenOutputs,
                 Optional.empty(), Optional.empty(), children);
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies.Builder builder = new FunctionalDependencies
+                
.Builder(child(0).getLogicalProperties().getFunctionalDependencies());
+        Map<Slot, Slot> replaceMap = new HashMap<>();
+        List<Slot> output = outputSupplier.get();
+        List<? extends Slot> originalOutputs = regularChildrenOutputs.isEmpty()
+                ? child(0).getOutput()
+                : regularChildrenOutputs.get(0);
+        for (int i = 0; i < output.size(); i++) {
+            replaceMap.put(originalOutputs.get(i), output.get(i));
+        }
+        builder.replace(replaceMap);
+        if (qualifier == Qualifier.DISTINCT) {
+            builder.addUniqueSlot(ImmutableSet.copyOf(outputSupplier.get()));
+        }
+        return builder.build();
+    }
 }
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 0dda497d285..3ca559c5197 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
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.algebra.Sink;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 
@@ -36,7 +37,8 @@ import java.util.Optional;
 /**
  * logicalFileSink for select into outfile
  */
-public class LogicalFileSink<CHILD_TYPE extends Plan> extends 
LogicalSink<CHILD_TYPE> implements Sink {
+public class LogicalFileSink<CHILD_TYPE extends Plan> extends 
LogicalSink<CHILD_TYPE>
+        implements Sink, PropagateFuncDeps {
 
     private final String filePath;
     private final String format;
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 cb5502d1b69..0c4699fcbe2 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
@@ -18,6 +18,8 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.properties.FunctionalDependencies.Builder;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
@@ -26,6 +28,7 @@ import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.algebra.Filter;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.nereids.util.ExpressionUtils;
 import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.base.Preconditions;
@@ -37,6 +40,7 @@ import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -136,4 +140,12 @@ public class LogicalFilter<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_T
     public LogicalFilter<Plan> withConjunctsAndChild(Set<Expression> 
conjuncts, Plan child) {
         return new LogicalFilter<>(conjuncts, child);
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        Builder fdBuilder = new Builder(
+                child().getLogicalProperties().getFunctionalDependencies());
+        getConjuncts().forEach(e -> 
fdBuilder.addUniformSlot(ExpressionUtils.extractUniformSlot(e)));
+        return fdBuilder.build();
+    }
 }
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 8036afe2563..0de6e46ce25 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
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
@@ -35,6 +36,7 @@ import com.google.common.collect.Lists;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * plan for table generator, the statement like: SELECT * FROM tbl LATERAL 
VIEW EXPLODE(c1) g as (gc1);
@@ -139,4 +141,11 @@ public class LogicalGenerate<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD
     public int hashCode() {
         return Objects.hash(generators, generatorOutput);
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies.Builder builder = new 
FunctionalDependencies.Builder();
+        
builder.addUniformSlot(child(0).getLogicalProperties().getFunctionalDependencies());
+        return builder.build();
+    }
 }
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 8524b35d827..25600c56d26 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
@@ -18,6 +18,8 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.properties.FunctionalDependencies.Builder;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
@@ -25,6 +27,7 @@ import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.algebra.Filter;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.nereids.util.ExpressionUtils;
 import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.base.Preconditions;
@@ -35,6 +38,7 @@ import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Supplier;
 
 /**
  * Logical Having plan
@@ -114,6 +118,14 @@ public class LogicalHaving<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_T
         return conjuncts.equals(other.conjuncts);
     }
 
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        Builder fdBuilder = new Builder(
+                child().getLogicalProperties().getFunctionalDependencies());
+        getConjuncts().forEach(e -> 
fdBuilder.addUniformSlot(ExpressionUtils.extractUniformSlot(e)));
+        return fdBuilder.build();
+    }
+
     @Override
     public String toString() {
         return Utils.toSqlString("LogicalHaving", "predicates", 
getPredicate());
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 3f08f4e10e4..2d3006bda59 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
@@ -18,8 +18,10 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
@@ -27,9 +29,13 @@ import 
org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * Logical Intersect.
@@ -98,4 +104,27 @@ public class LogicalIntersect extends LogicalSetOperation {
         return new LogicalIntersect(qualifier, newOutputs, 
regularChildrenOutputs,
                 Optional.empty(), Optional.empty(), children);
     }
+
+    void replaceSlotInFuncDeps(FunctionalDependencies.Builder builder,
+            List<Slot> originalOutputs, List<Slot> newOutputs) {
+        Map<Slot, Slot> replaceMap = new HashMap<>();
+        for (int i = 0; i < newOutputs.size(); i++) {
+            replaceMap.put(originalOutputs.get(i), newOutputs.get(i));
+        }
+        builder.replace(replaceMap);
+    }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies.Builder builder = new 
FunctionalDependencies.Builder();
+        for (Plan child : children) {
+            builder.addFunctionalDependencies(
+                    child.getLogicalProperties().getFunctionalDependencies());
+            replaceSlotInFuncDeps(builder, child.getOutput(), 
outputSupplier.get());
+        }
+        if (qualifier == Qualifier.DISTINCT) {
+            builder.addUniqueSlot(ImmutableSet.copyOf(outputSupplier.get()));
+        }
+        return builder.build();
+    }
 }
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 b5459acf35b..c898e12e2f2 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
@@ -17,10 +17,14 @@
 
 package org.apache.doris.nereids.trees.plans.logical;
 
+import org.apache.doris.common.Pair;
 import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.LongBitmap;
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.properties.FunctionalDependencies.Builder;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.rules.exploration.join.JoinReorderContext;
+import org.apache.doris.nereids.trees.expressions.EqualTo;
 import org.apache.doris.nereids.trees.expressions.ExprId;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.MarkJoinSlotReference;
@@ -37,17 +41,19 @@ import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import org.json.JSONObject;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import javax.annotation.Nullable;
 
 /**
  * Logical join plan.
@@ -269,7 +275,7 @@ public class LogicalJoin<LEFT_CHILD_TYPE extends Plan, 
RIGHT_CHILD_TYPE extends
 
     @Override
     public List<? extends Expression> getExpressions() {
-        return new Builder<Expression>()
+        return new ImmutableList.Builder<Expression>()
                 .addAll(hashJoinConjuncts)
                 .addAll(otherJoinConjuncts)
                 .build();
@@ -357,6 +363,92 @@ public class LogicalJoin<LEFT_CHILD_TYPE extends Plan, 
RIGHT_CHILD_TYPE extends
                 markJoinSlotReference, children);
     }
 
+    private @Nullable Pair<Set<Slot>, Set<Slot>> extractHashKeys() {
+        Set<Slot> leftKeys = new HashSet<>();
+        Set<Slot> rightKeys = new HashSet<>();
+        for (Expression expression : hashJoinConjuncts) {
+            // Note we don't support null-safe predicate right now, because we 
just check uniqueness for join keys
+            if (!(expression instanceof EqualTo
+                    && ((EqualTo) expression).left() instanceof Slot
+                    && ((EqualTo) expression).right() instanceof Slot)) {
+                return null;
+            }
+            Slot leftKey = (Slot) ((EqualTo) expression).left();
+            Slot rightKey = (Slot) ((EqualTo) expression).right();
+            if (left().getOutputSet().contains(leftKey)) {
+                leftKeys.add(leftKey);
+                rightKeys.add(rightKey);
+            } else {
+                leftKeys.add(rightKey);
+                rightKeys.add(leftKey);
+            }
+        }
+        return Pair.of(leftKeys, rightKeys);
+    }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        //1. NALAJ and FOJ block functional dependencies
+        if (joinType.isNullAwareLeftAntiJoin() || joinType.isFullOuterJoin()) {
+            return FunctionalDependencies.EMPTY_FUNC_DEPS;
+        }
+
+        // left/right semi/anti join propagate left/right functional 
dependencies
+        if (joinType.isLeftAntiJoin() || joinType.isLefSemiJoin()) {
+            return left().getLogicalProperties().getFunctionalDependencies();
+        }
+        if (joinType.isRightSemiJoin() || joinType.isRightAntiJoin()) {
+            return right().getLogicalProperties().getFunctionalDependencies();
+        }
+
+        // if there is non-equal join conditions, block functional dependencies
+        if (!otherJoinConjuncts.isEmpty()) {
+            return FunctionalDependencies.EMPTY_FUNC_DEPS;
+        }
+
+        Pair<Set<Slot>, Set<Slot>> keys = extractHashKeys();
+        if (keys == null) {
+            return FunctionalDependencies.EMPTY_FUNC_DEPS;
+        }
+
+        // Note here we only check whether the left is unique.
+        // So the hash condition can't be null-safe
+        // TODO: consider Null-safe hash condition when left and rigth is not 
nullable
+        boolean isLeftUnique = left().getLogicalProperties()
+                .getFunctionalDependencies().isUnique(keys.first);
+        boolean isRightUnique = left().getLogicalProperties()
+                .getFunctionalDependencies().isUnique(keys.first);
+        Builder fdBuilder = new Builder();
+        if (joinType.isInnerJoin()) {
+            // inner join propagate uniforms slots
+            // And if the hash keys is unique, inner join can propagate all 
functional dependencies
+            if (isLeftUnique && isRightUnique) {
+                
fdBuilder.addFunctionalDependencies(left().getLogicalProperties().getFunctionalDependencies());
+                
fdBuilder.addFunctionalDependencies(right().getLogicalProperties().getFunctionalDependencies());
+            } else {
+                
fdBuilder.addUniformSlot(left().getLogicalProperties().getFunctionalDependencies());
+                
fdBuilder.addUniformSlot(right().getLogicalProperties().getFunctionalDependencies());
+            }
+        }
+
+        // left/right outer join propagate left/right uniforms slots
+        // And if the right/left hash keys is unique,
+        // join can propagate left/right functional dependencies
+        if (joinType.isLeftOuterJoin()) {
+            if (isRightUnique) {
+                return 
left().getLogicalProperties().getFunctionalDependencies();
+            }
+            
fdBuilder.addUniformSlot(left().getLogicalProperties().getFunctionalDependencies());
+        }
+        if (joinType.isRightOuterJoin()) {
+            if (isLeftUnique) {
+                return 
left().getLogicalProperties().getFunctionalDependencies();
+            }
+            
fdBuilder.addUniformSlot(left().getLogicalProperties().getFunctionalDependencies());
+        }
+        return fdBuilder.build();
+    }
+
     @Override
     public JSONObject toJson() {
         JSONObject logicalJoin = super.toJson();
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 e896cf22402..df18b134f10 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
@@ -18,6 +18,8 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.properties.FunctionalDependencies.Builder;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
@@ -34,6 +36,7 @@ import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * Logical limit plan
@@ -140,4 +143,16 @@ public class LogicalLimit<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TY
         Preconditions.checkArgument(children.size() == 1);
         return new LogicalLimit<>(limit, offset, phase, children.get(0));
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies fd = 
child(0).getLogicalProperties().getFunctionalDependencies();
+        if (getLimit() == 1 && !phase.isLocal()) {
+            Builder builder = new Builder();
+            outputSupplier.get().forEach(builder::addUniformSlot);
+            outputSupplier.get().forEach(builder::addUniqueSlot);
+            fd = builder.build();
+        }
+        return fd;
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOlapTableSink.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOlapTableSink.java
index a3641057a7a..664ac8f9936 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOlapTableSink.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOlapTableSink.java
@@ -25,6 +25,7 @@ import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.algebra.Sink;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
@@ -39,7 +40,8 @@ import java.util.Optional;
 /**
  * logical olap table sink for insert command
  */
-public class LogicalOlapTableSink<CHILD_TYPE extends Plan> extends 
LogicalSink<CHILD_TYPE> implements Sink {
+public class LogicalOlapTableSink<CHILD_TYPE extends Plan> extends 
LogicalSink<CHILD_TYPE>
+        implements Sink, PropagateFuncDeps {
     // bound data sink
     private final Database database;
     private final OlapTable targetTable;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOneRowRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOneRowRelation.java
index ffc7c0d625a..c0c25f79693 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOneRowRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOneRowRelation.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
@@ -34,6 +35,7 @@ import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * A relation that contains only one row consist of some constant expressions.
@@ -126,4 +128,14 @@ public class LogicalOneRowRelation extends LogicalRelation 
implements OneRowRela
     public Plan pruneOutputs(List<NamedExpression> prunedOutputs) {
         return withProjects(prunedOutputs);
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies.Builder builder = new 
FunctionalDependencies.Builder();
+        outputSupplier.get().forEach(s -> {
+            builder.addUniformSlot(s);
+            builder.addUniqueSlot(s);
+        });
+        return builder.build();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalPartitionTopN.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalPartitionTopN.java
index 003b2737277..85ee1e25dee 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalPartitionTopN.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalPartitionTopN.java
@@ -28,6 +28,7 @@ import 
org.apache.doris.nereids.trees.expressions.functions.window.Rank;
 import org.apache.doris.nereids.trees.expressions.functions.window.RowNumber;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.WindowFuncType;
 import org.apache.doris.nereids.trees.plans.algebra.PartitionTopN;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
@@ -43,7 +44,8 @@ import java.util.Optional;
 /**
  * Logical partition-top-N plan.
  */
-public class LogicalPartitionTopN<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> implements PartitionTopN {
+public class LogicalPartitionTopN<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE>
+        implements PartitionTopN, PropagateFuncDeps {
     private final WindowFuncType function;
     private final List<Expression> partitionKeys;
     private final List<OrderExpression> orderKeys;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalPlan.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalPlan.java
index 15b289415d9..3c058c6e4d7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalPlan.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalPlan.java
@@ -17,10 +17,13 @@
 
 package org.apache.doris.nereids.trees.plans.logical;
 
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.Plan;
 
 import com.google.common.collect.ImmutableList;
 
+import java.util.List;
 import java.util.Optional;
 import java.util.function.BiFunction;
 import java.util.function.Supplier;
@@ -50,4 +53,11 @@ public interface LogicalPlan extends Plan {
         return (LogicalPlan) withChildren(ImmutableList.copyOf(children()));
     }
 
+    /**
+     * Compute FunctionalDependencies for different plan
+     * Note: Unless you really know what you're doing, please use the 
following interface.
+     *   - BlockFDPropagation: clean the fd
+     *   - PropagateFD: propagate the fd
+     */
+    FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier);
 }
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 46b3f3d84a1..af2861ad19e 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
@@ -20,11 +20,14 @@ package org.apache.doris.nereids.trees.plans.logical;
 import org.apache.doris.nereids.analyzer.Unbound;
 import org.apache.doris.nereids.analyzer.UnboundStar;
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
+import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.BoundStar;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Uuid;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.algebra.Project;
@@ -34,11 +37,14 @@ import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import org.json.JSONObject;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * Logical project plan.
@@ -200,4 +206,26 @@ public class LogicalProject<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_
         logicalProject.put("Properties", properties);
         return logicalProject;
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies childFuncDeps = 
child().getLogicalProperties().getFunctionalDependencies();
+        FunctionalDependencies.Builder builder = new 
FunctionalDependencies.Builder(childFuncDeps);
+        builder.pruneSlots(new HashSet<>(outputSupplier.get()));
+        projects.stream().filter(Alias.class::isInstance).forEach(proj -> {
+            if (proj.child(0).isConstant()) {
+                builder.addUniformSlot(proj.toSlot());
+            } else if (proj.child(0) instanceof Uuid) {
+                builder.addUniqueSlot(proj.toSlot());
+            } else if (ExpressionUtils.isInjective(proj.child(0))) {
+                ImmutableSet<Slot> inputs = 
ImmutableSet.copyOf(proj.getInputSlots());
+                if (childFuncDeps.isUnique(inputs)) {
+                    builder.addUniqueSlot(proj.toSlot());
+                } else if (childFuncDeps.isUniform(inputs)) {
+                    builder.addUniformSlot(proj.toSlot());
+                }
+            }
+        });
+        return builder.build();
+    }
 }
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 61fc8584d40..8ee9e465a29 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
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
@@ -36,6 +37,7 @@ import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * LogicalRepeat.
@@ -177,4 +179,14 @@ public class LogicalRepeat<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_T
         return bound() && outputExpressions.stream()
                 .noneMatch(output -> 
output.containsType(VirtualSlotReference.class));
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies.Builder builder = new 
FunctionalDependencies.Builder();
+        // Note uniform does not reject nullable slots
+        outputSupplier.get().stream()
+                
.filter(child(0).getLogicalProperties().getFunctionalDependencies()::isUniform)
+                .forEach(builder::addUniformSlot);
+        return builder.build();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalResultSink.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalResultSink.java
index 6e754b491a9..564938dd8b1 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalResultSink.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalResultSink.java
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.algebra.Sink;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
@@ -34,7 +35,8 @@ import java.util.Optional;
 /**
  * result sink
  */
-public class LogicalResultSink<CHILD_TYPE extends Plan> extends 
LogicalSink<CHILD_TYPE> implements Sink {
+public class LogicalResultSink<CHILD_TYPE extends Plan> extends 
LogicalSink<CHILD_TYPE>
+        implements Sink, PropagateFuncDeps {
 
     public LogicalResultSink(List<NamedExpression> outputExprs, CHILD_TYPE 
child) {
         super(PlanType.LOGICAL_RESULT_SINK, outputExprs, child);
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 260d29a1ab4..1595666285e 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
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.properties.SelectHint;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
@@ -40,7 +41,8 @@ import java.util.stream.Collectors;
  * select hint plan.
  * e.g. LogicalSelectHint (set_var(query_timeout='1800', 
exec_mem_limit='2147483648'))
  */
-public class LogicalSelectHint<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> {
+public class LogicalSelectHint<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE>
+        implements BlockFuncDepsPropagation {
 
     private final Map<String, SelectHint> hints;
 
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 f139656f01c..9d9d321e659 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
@@ -24,6 +24,7 @@ import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.PropagateFuncDeps;
 import org.apache.doris.nereids.trees.plans.algebra.Sort;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
@@ -42,7 +43,8 @@ import java.util.Optional;
  * orderKeys: list of column information after order by. eg:[a, asc],[b, desc].
  * OrderKey: Contains order expression information and sorting method. Default 
is ascending.
  */
-public class LogicalSort<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> implements Sort {
+public class LogicalSort<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE>
+        implements Sort, PropagateFuncDeps {
 
     private final List<OrderKey> orderKeys;
 
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 5bef42f4f2c..5dff119af8f 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
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
@@ -30,9 +31,12 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import org.apache.commons.lang3.StringUtils;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * The node of logical plan for sub query and alias
@@ -153,4 +157,17 @@ public class LogicalSubQueryAlias<CHILD_TYPE extends Plan> 
extends LogicalUnary<
         return new LogicalSubQueryAlias<>(qualifier, columnAliases, 
groupExpression, logicalProperties,
                 children.get(0));
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies.Builder builder = new FunctionalDependencies
+                
.Builder(child(0).getLogicalProperties().getFunctionalDependencies());
+        Map<Slot, Slot> replaceMap = new HashMap<>();
+        List<Slot> outputs = outputSupplier.get();
+        for (int i = 0; i < outputs.size(); i++) {
+            replaceMap.put(child(0).getOutput().get(i), outputs.get(i));
+        }
+        builder.replace(replaceMap);
+        return builder.build();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTVFRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTVFRelation.java
index 4527ffa3162..f5842c30dc1 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTVFRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTVFRelation.java
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
 import 
org.apache.doris.nereids.trees.expressions.functions.table.TableValuedFunction;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.RelationId;
@@ -37,7 +38,7 @@ import java.util.Objects;
 import java.util.Optional;
 
 /** LogicalTableValuedFunctionRelation */
-public class LogicalTVFRelation extends LogicalRelation implements TVFRelation 
{
+public class LogicalTVFRelation extends LogicalRelation implements 
TVFRelation, BlockFuncDepsPropagation {
 
     private final TableValuedFunction function;
     private final ImmutableList<String> qualifier;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTopN.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTopN.java
index 02b239f1735..a38778d8d50 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTopN.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTopN.java
@@ -18,6 +18,8 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
+import org.apache.doris.nereids.properties.FunctionalDependencies.Builder;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.properties.OrderKey;
 import org.apache.doris.nereids.trees.expressions.Expression;
@@ -34,6 +36,7 @@ import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * Logical top-N plan.
@@ -148,4 +151,17 @@ public class LogicalTopN<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYP
         Preconditions.checkArgument(children.size() == 1);
         return new LogicalTopN<>(orderKeys, limit, offset, groupExpression, 
logicalProperties, children.get(0));
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies fd = 
child(0).getLogicalProperties().getFunctionalDependencies();
+        if (getLimit() == 1) {
+            Builder builder = new Builder();
+            List<Slot> output = outputSupplier.get();
+            output.forEach(builder::addUniformSlot);
+            output.forEach(builder::addUniqueSlot);
+            fd = builder.build();
+        }
+        return fd;
+    }
 }
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 a5ab24e8397..17c4ef4a51c 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
@@ -18,9 +18,11 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
@@ -30,10 +32,12 @@ import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * Logical Union.
@@ -170,4 +174,14 @@ public class LogicalUnion extends LogicalSetOperation 
implements Union, OutputPr
     public LogicalUnion pruneOutputs(List<NamedExpression> prunedOutputs) {
         return withNewOutputs(prunedOutputs);
     }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        if (qualifier != Qualifier.DISTINCT) {
+            return FunctionalDependencies.EMPTY_FUNC_DEPS;
+        }
+        FunctionalDependencies.Builder builder = new 
FunctionalDependencies.Builder();
+        builder.addUniqueSlot(ImmutableSet.copyOf(outputSupplier.get()));
+        return builder.build();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalWindow.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalWindow.java
index fcfe9906d60..e8c16b49026 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalWindow.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalWindow.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.trees.plans.logical;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
@@ -36,10 +37,12 @@ import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Supplier;
 
 /**
  * logical node to deal with window functions;
@@ -224,4 +227,46 @@ public class LogicalWindow<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_T
 
         return Optional.ofNullable(window);
     }
+
+    private void updateFuncDepsByWindowExpr(NamedExpression namedExpression, 
FunctionalDependencies.Builder builder) {
+        if (namedExpression.children().size() != 1 || 
!(namedExpression.child(0) instanceof WindowExpression)) {
+            return;
+        }
+        WindowExpression windowExpr = (WindowExpression) 
namedExpression.child(0);
+        List<Expression> partitionKeys = windowExpr.getPartitionKeys();
+
+        // Now we only support slot type keys
+        if (!partitionKeys.stream().allMatch(Slot.class::isInstance)) {
+            return;
+        }
+        ImmutableSet<Slot> slotSet = partitionKeys.stream()
+                .map(s -> (Slot) s)
+                .collect(ImmutableSet.toImmutableSet());
+
+        // if partition by keys are unique, output is uniform
+        if 
(child(0).getLogicalProperties().getFunctionalDependencies().isUniqueAndNotNull(slotSet))
 {
+            if (windowExpr.getFunction() instanceof RowNumber
+                    || windowExpr.getFunction() instanceof Rank
+                    || windowExpr.getFunction() instanceof DenseRank) {
+                builder.addUniformSlot(namedExpression.toSlot());
+            }
+        }
+
+        // if partition by keys are uniform, output is unique
+        if 
(child(0).getLogicalProperties().getFunctionalDependencies().isUniformAndNotNull(slotSet))
 {
+            if (windowExpr.getFunction() instanceof RowNumber) {
+                builder.addUniqueSlot(namedExpression.toSlot());
+            }
+        }
+    }
+
+    @Override
+    public FunctionalDependencies computeFuncDeps(Supplier<List<Slot>> 
outputSupplier) {
+        FunctionalDependencies.Builder builder = new 
FunctionalDependencies.Builder(
+                child(0).getLogicalProperties().getFunctionalDependencies());
+        for (NamedExpression namedExpression : windowExpressions) {
+            updateFuncDepsByWindowExpr(namedExpression, builder);
+        }
+        return builder.build();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/UsingJoin.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/UsingJoin.java
index dd4ffbe7967..4796b33e1fe 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/UsingJoin.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/UsingJoin.java
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.MarkJoinSlotReference;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.BlockFuncDepsPropagation;
 import org.apache.doris.nereids.trees.plans.JoinHint;
 import org.apache.doris.nereids.trees.plans.JoinType;
 import org.apache.doris.nereids.trees.plans.Plan;
@@ -41,7 +42,7 @@ import java.util.Optional;
  * select col1 from t1 join t2 using(col1);
  */
 public class UsingJoin<LEFT_CHILD_TYPE extends Plan, RIGHT_CHILD_TYPE extends 
Plan>
-        extends LogicalBinary<LEFT_CHILD_TYPE, RIGHT_CHILD_TYPE> implements 
Join {
+        extends LogicalBinary<LEFT_CHILD_TYPE, RIGHT_CHILD_TYPE> implements 
Join, BlockFuncDepsPropagation {
 
     private final JoinType joinType;
     private final ImmutableList<Expression> otherJoinConjuncts;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java
index 4793395cfe3..c27254a7ed0 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java
@@ -37,6 +37,11 @@ import org.apache.doris.nereids.trees.expressions.Not;
 import org.apache.doris.nereids.trees.expressions.Or;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
+import 
org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
+import org.apache.doris.nereids.trees.expressions.functions.agg.Avg;
+import org.apache.doris.nereids.trees.expressions.functions.agg.Max;
+import org.apache.doris.nereids.trees.expressions.functions.agg.Min;
+import org.apache.doris.nereids.trees.expressions.functions.agg.Sum;
 import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
@@ -444,6 +449,32 @@ public class ExpressionUtils {
                 .collect(ImmutableSet.toImmutableSet());
     }
 
+    /**
+     * extract uniform slot for the given predicate, such as a = 1 and b = 2
+     */
+    public static ImmutableSet<Slot> extractUniformSlot(Expression expression) 
{
+        ImmutableSet.Builder<Slot> builder = new ImmutableSet.Builder<>();
+        if (expression instanceof And) {
+            builder.addAll(extractUniformSlot(expression.child(0)));
+            builder.addAll(extractUniformSlot(expression.child(1)));
+        }
+        if (expression instanceof EqualTo) {
+            if (isInjective(expression.child(0)) && 
expression.child(1).isConstant()) {
+                builder.add((Slot) expression.child(0));
+            }
+        }
+        return builder.build();
+    }
+
+    // TODO: Add more injective functions
+    public static boolean isInjective(Expression expression) {
+        return expression instanceof Slot;
+    }
+
+    public static boolean isInjectiveAgg(AggregateFunction agg) {
+        return agg instanceof Sum || agg instanceof Avg || agg instanceof Max 
|| agg instanceof Min;
+    }
+
     public static <E> Set<E> mutableCollect(List<? extends Expression> 
expressions,
             Predicate<TreeNode<Expression>> predicate) {
         return expressions.stream()
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslatorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslatorTest.java
index ad8ed9b6bf1..fbe87e079c1 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslatorTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslatorTest.java
@@ -19,6 +19,7 @@ package org.apache.doris.nereids.glue.translator;
 
 import org.apache.doris.catalog.KeysType;
 import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.GreaterThan;
@@ -63,7 +64,7 @@ public class PhysicalPlanTranslatorTest {
         t1Output.add(col1);
         t1Output.add(col2);
         t1Output.add(col3);
-        LogicalProperties t1Properties = new LogicalProperties(() -> t1Output);
+        LogicalProperties t1Properties = new LogicalProperties(() -> t1Output, 
() -> FunctionalDependencies.EMPTY_FUNC_DEPS);
         PhysicalOlapScan scan = new 
PhysicalOlapScan(StatementScopeIdGenerator.newRelationId(), t1, qualifier, 
t1.getBaseIndexId(),
                 Collections.emptyList(), Collections.emptyList(), null, 
PreAggStatus.on(),
                 ImmutableList.of(), Optional.empty(), t1Properties, 
Optional.empty());
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/cascades/DeriveStatsJobTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/cascades/DeriveStatsJobTest.java
index fd60eb2e00c..0b63dacf782 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/cascades/DeriveStatsJobTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/cascades/DeriveStatsJobTest.java
@@ -22,6 +22,7 @@ import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.nereids.CascadesContext;
 import org.apache.doris.nereids.jobs.JobContext;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.ExprId;
@@ -87,7 +88,7 @@ public class DeriveStatsJobTest {
         OlapTable table1 = PlanConstructor.newOlapTable(tableId1, "t1", 0);
         return (LogicalOlapScan) new 
LogicalOlapScan(StatementScopeIdGenerator.newRelationId(), table1,
                 
Collections.emptyList()).withGroupExprLogicalPropChildren(Optional.empty(),
-                Optional.of(new LogicalProperties(() -> 
ImmutableList.of(slot1))), ImmutableList.of());
+                Optional.of(new LogicalProperties(() -> 
ImmutableList.of(slot1), () -> FunctionalDependencies.EMPTY_FUNC_DEPS)), 
ImmutableList.of());
     }
 
     private LogicalAggregate constructAgg(Plan child) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/memo/MemoTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/memo/MemoTest.java
index 62d62ebf432..8943787a300 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/nereids/memo/MemoTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/memo/MemoTest.java
@@ -22,6 +22,7 @@ import org.apache.doris.common.jmockit.Deencapsulation;
 import org.apache.doris.nereids.analyzer.UnboundRelation;
 import org.apache.doris.nereids.analyzer.UnboundSlot;
 import org.apache.doris.nereids.cost.Cost;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.properties.PhysicalProperties;
 import org.apache.doris.nereids.properties.UnboundLogicalProperties;
@@ -89,16 +90,18 @@ class MemoTest implements MemoPatternMatchSupported {
     @Test
     void testMergeGroup() {
         Group srcGroup = new Group(new GroupId(2), new GroupExpression(new 
FakePlan()),
-                new LogicalProperties(ArrayList::new));
+                new LogicalProperties(ArrayList::new, () -> 
FunctionalDependencies.EMPTY_FUNC_DEPS));
         Group dstGroup = new Group(new GroupId(3), new GroupExpression(new 
FakePlan()),
-                new LogicalProperties(ArrayList::new));
+                new LogicalProperties(ArrayList::new, () -> 
FunctionalDependencies.EMPTY_FUNC_DEPS));
 
         FakePlan fakePlan = new FakePlan();
         GroupExpression srcParentExpression = new GroupExpression(fakePlan, 
Lists.newArrayList(srcGroup));
-        Group srcParentGroup = new Group(new GroupId(0), srcParentExpression, 
new LogicalProperties(ArrayList::new));
+        Group srcParentGroup = new Group(new GroupId(0), srcParentExpression,
+                new LogicalProperties(ArrayList::new, () -> 
FunctionalDependencies.EMPTY_FUNC_DEPS));
         srcParentGroup.setBestPlan(srcParentExpression, Cost.zeroV1(), 
PhysicalProperties.ANY);
         GroupExpression dstParentExpression = new GroupExpression(fakePlan, 
Lists.newArrayList(dstGroup));
-        Group dstParentGroup = new Group(new GroupId(1), dstParentExpression, 
new LogicalProperties(ArrayList::new));
+        Group dstParentGroup = new Group(new GroupId(1), dstParentExpression,
+                new LogicalProperties(ArrayList::new, () -> 
FunctionalDependencies.EMPTY_FUNC_DEPS));
 
         Memo memo = new Memo();
         Map<GroupId, Group> groups = Deencapsulation.getField(memo, "groups");
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/MergeProjectPostProcessTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/MergeProjectPostProcessTest.java
index 729d988d93d..4b273b55087 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/MergeProjectPostProcessTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/MergeProjectPostProcessTest.java
@@ -21,6 +21,7 @@ import org.apache.doris.catalog.KeysType;
 import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.nereids.CascadesContext;
 import org.apache.doris.nereids.processor.post.MergeProjectPostProcessor;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
@@ -74,7 +75,7 @@ public class MergeProjectPostProcessTest {
         t1Output.add(a);
         t1Output.add(b);
         t1Output.add(c);
-        LogicalProperties t1Properties = new LogicalProperties(() -> t1Output);
+        LogicalProperties t1Properties = new LogicalProperties(() -> t1Output, 
() -> FunctionalDependencies.EMPTY_FUNC_DEPS);
         PhysicalOlapScan scan = new 
PhysicalOlapScan(RelationId.createGenerator().getNextId(), t1, qualifier, 0L,
                 Collections.emptyList(), Collections.emptyList(), null, 
PreAggStatus.on(), ImmutableList.of(),
                 Optional.empty(), t1Properties, Optional.empty());
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/PushDownFilterThroughProjectTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/PushDownFilterThroughProjectTest.java
index b817f800008..08ccc56f9ba 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/PushDownFilterThroughProjectTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/PushDownFilterThroughProjectTest.java
@@ -21,6 +21,7 @@ import org.apache.doris.catalog.KeysType;
 import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.nereids.CascadesContext;
 import org.apache.doris.nereids.processor.post.PushDownFilterThroughProject;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.EqualTo;
@@ -85,7 +86,7 @@ public class PushDownFilterThroughProjectTest {
         t1Output.add(a);
         t1Output.add(b);
         t1Output.add(c);
-        LogicalProperties t1Properties = new LogicalProperties(() -> t1Output);
+        LogicalProperties t1Properties = new LogicalProperties(() -> t1Output, 
() -> FunctionalDependencies.EMPTY_FUNC_DEPS);
         PhysicalOlapScan scan = new 
PhysicalOlapScan(RelationId.createGenerator().getNextId(), t1,
                 qualifier, 0L, Collections.emptyList(), 
Collections.emptyList(), null,
                 PreAggStatus.on(), ImmutableList.of(), Optional.empty(), 
t1Properties,
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/properties/FunctionalDependenciesTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/properties/FunctionalDependenciesTest.java
new file mode 100644
index 00000000000..ff72b8cb4ba
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/properties/FunctionalDependenciesTest.java
@@ -0,0 +1,387 @@
+// 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.properties;
+
+import org.apache.doris.nereids.properties.FunctionalDependencies.Builder;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPartitionTopN;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.utframe.TestWithFeService;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+class FunctionalDependenciesTest extends TestWithFeService {
+    Slot slot1 = new SlotReference("1", IntegerType.INSTANCE, false);
+    Slot slot2 = new SlotReference("2", IntegerType.INSTANCE, false);
+    Slot slot3 = new SlotReference("1", IntegerType.INSTANCE, false);
+    Slot slot4 = new SlotReference("1", IntegerType.INSTANCE, false);
+
+    @Override
+    protected void runBeforeAll() throws Exception {
+        createDatabase("test");
+        createTable("create table test.agg (\n"
+                + "id int not null,\n"
+                + "name varchar(128) replace not null )\n"
+                + "AGGREGATE KEY(id)\n"
+                + "distributed by hash(id) buckets 10\n"
+                + "properties('replication_num' = '1');");
+        createTable("create table test.uni (\n"
+                + "id int not null,\n"
+                + "name varchar(128) not null)\n"
+                + "UNIQUE KEY(id)\n"
+                + "distributed by hash(id) buckets 10\n"
+                + "properties('replication_num' = '1');");
+        connectContext.setDatabase("default_cluster:test");
+    }
+
+    @Test
+    void testUniform() {
+        Builder fdBuilder = new Builder();
+        fdBuilder.addUniformSlot(slot1);
+        FunctionalDependencies fd = fdBuilder.build();
+        Assertions.assertTrue(fd.isUniformAndNotNull(slot1));
+        Assertions.assertFalse(fd.isUniformAndNotNull(slot2));
+        fdBuilder.addUniformSlot(ImmutableSet.of(slot2));
+        fd = fdBuilder.build();
+        Assertions.assertTrue(fd.isUniformAndNotNull(slot2));
+        ImmutableSet<Slot> slotSet = ImmutableSet.of(slot1, slot2, slot3);
+        fdBuilder.addUniformSlot(slotSet);
+        fd = fdBuilder.build();
+        Assertions.assertTrue(fd.isUniformAndNotNull(slotSet));
+        Assertions.assertFalse(fd.isUniformAndNotNull(ImmutableSet.of(slot1, 
slot2, slot3, slot4)));
+        Assertions.assertTrue(fd.isUniformAndNotNull(ImmutableSet.of(slot1, 
slot2)));
+        Assertions.assertFalse(fd.isUniformAndNotNull(ImmutableSet.of(slot3, 
slot2)));
+    }
+
+    @Test
+    void testUnique() {
+        Builder fdBuilder = new Builder();
+        fdBuilder.addUniqueSlot(slot1);
+        FunctionalDependencies fd = fdBuilder.build();
+        Assertions.assertTrue(fd.isUniqueAndNotNull(slot1));
+        Assertions.assertFalse(fd.isUniqueAndNotNull(slot2));
+        fdBuilder.addUniqueSlot(slot2);
+        fd = fdBuilder.build();
+        Assertions.assertTrue(fd.isUniqueAndNotNull(slot2));
+        ImmutableSet<Slot> slotSet = ImmutableSet.of(slot1, slot2, slot3);
+        fdBuilder.addUniqueSlot(slotSet);
+        fd = fdBuilder.build();
+        Assertions.assertTrue(fd.isUniqueAndNotNull(slotSet));
+        Assertions.assertTrue(fd.isUniqueAndNotNull(ImmutableSet.of(slot1, 
slot2, slot3, slot4)));
+        Assertions.assertFalse(fd.isUniqueAndNotNull(ImmutableSet.of(slot3, 
slot4)));
+    }
+
+    @Test
+    void testMergeFD() {
+        Builder fdBuilder1 = new Builder();
+        fdBuilder1.addUniformSlot(slot1);
+        Builder fdBuilder2 = new Builder();
+        fdBuilder2.addUniformSlot(slot2);
+
+        fdBuilder1.addFunctionalDependencies(fdBuilder2.build());
+        FunctionalDependencies fd = fdBuilder1.build();
+        Assertions.assertTrue(fd.isUniformAndNotNull(slot1));
+        Assertions.assertTrue(fd.isUniformAndNotNull(slot2));
+    }
+
+    @Test
+    void testScan() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select id from agg")
+                .rewrite()
+                .getPlan();
+        
Assertions.assertTrue(plan.getLogicalProperties().getFunctionalDependencies()
+                .isUniqueAndNotNull(plan.getOutput().get(0)));
+        plan = PlanChecker.from(connectContext)
+                .analyze("select id from uni")
+                .rewrite()
+                .getPlan();
+        
Assertions.assertTrue(plan.getLogicalProperties().getFunctionalDependencies()
+                .isUniqueAndNotNull(plan.getOutput().get(0)));
+    }
+
+    @Test
+    void testFilter() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select name from agg where name = \"1\"")
+                .rewrite()
+                .getPlan();
+        
Assertions.assertTrue(plan.getLogicalProperties().getFunctionalDependencies()
+                .isUniformAndNotNull(plan.getOutput().get(0)));
+    }
+
+    @Test
+    void testJoin() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select * from agg full outer join uni "
+                        + "on agg.id = uni.id")
+                .rewrite()
+                .getPlan();
+        
Assertions.assertTrue(plan.getLogicalProperties().getFunctionalDependencies().isEmpty());
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select agg.id, uni.id from agg full outer join uni "
+                        + "on agg.id = uni.id")
+                .rewrite()
+                .getPlan();
+        
Assertions.assertTrue(plan.getLogicalProperties().getFunctionalDependencies().isEmpty());
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select agg.id from agg left outer join uni "
+                        + "on agg.id = uni.id")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(0)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select agg.id from agg left semi join uni "
+                        + "on agg.id = uni.id")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(0)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select agg.id from agg left anti join uni "
+                        + "on agg.id = uni.id")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(0)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select uni.id from agg right outer join uni "
+                        + "on agg.id = uni.id")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(0)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select uni.id, agg.id from agg inner join uni "
+                        + "on agg.id = uni.id")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(0)));
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(1)));
+    }
+
+    @Test
+    void testAgg() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select count(1), name from agg group by name")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(1)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select count(1), max(id), id from agg group by id")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(1)));
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(2)));
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniformAndNotNull(plan.getOutput().get(0)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select count(1), id from agg where id = 1 group by 
id")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniformAndNotNull(plan.getOutput().get(1)));
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniformAndNotNull(plan.getOutput().get(0)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select count(name) from agg")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniformAndNotNull(plan.getOutput().get(0)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select id from agg where id = 1 group by cube(id, 
name)")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniform(plan.getOutput().get(0)));
+    }
+
+    @Test
+    void testGenerate() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select k1 from (select 1 k1) as t lateral view 
explode([1,2,3]) tmp1 as e1")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniform(plan.getOutput().get(0)));
+    }
+
+    @Test
+    void testWindow() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select row_number() over(partition by id) from agg")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniformAndNotNull(plan.getOutput().get(0)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select row_number() over(partition by name) from agg 
where name = '1'")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(0)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select row_number() over(partition by name) from agg 
where name = '1' limit 1")
+                .rewrite()
+                .getPlan();
+        LogicalPartitionTopN<?> ptopn = (LogicalPartitionTopN<?>) 
plan.child(0).child(0).child(0).child(0).child(0);
+        
System.out.println(ptopn.getLogicalProperties().getFunctionalDependencies());
+        System.out.println(ptopn.getOutput());
+        Assertions.assertTrue(ptopn.getLogicalProperties()
+                
.getFunctionalDependencies().isUniformAndNotNull(ImmutableSet.copyOf(ptopn.getOutputSet())));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select row_number() over(partition by name) from agg 
limit 1")
+                .rewrite()
+                .getPlan();
+        ptopn = (LogicalPartitionTopN<?>) 
plan.child(0).child(0).child(0).child(0).child(0);
+        Assertions.assertTrue(ptopn.getLogicalProperties()
+                .getFunctionalDependencies().isEmpty());
+    }
+
+    @Test
+    void testProject() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select name from agg where id = 1")
+                .rewrite()
+                .getPlan();
+        Assertions.assertTrue(plan.getLogicalProperties()
+                .getFunctionalDependencies().isEmpty());
+    }
+
+    @Test
+    void testLimitAndTopN() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select name from agg limit 1")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniformAndNotNull(plan.getOutput().get(0)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select name from agg order by id limit 1")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniformAndNotNull(plan.getOutput().get(0)));
+    }
+
+    @Test
+    void testSubQuery() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select id from (select * from agg) t")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(0)));
+    }
+
+    @Disabled
+    @Test
+    void testCTE() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("with t as (select * from agg) select id from t where 
id = 1")
+                .getPlan();
+        System.out.println(plan.treeString());
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(0)));
+    }
+
+    @Test
+    void testSetOperation() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select * from agg union select * from uni")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(ImmutableSet.copyOf(plan.getOutput())));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select * from agg union all select * from uni")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertFalse(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(ImmutableSet.copyOf(plan.getOutput())));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select * from agg intersect select * from uni where 
name = \"1\"")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(ImmutableSet.copyOf(plan.getOutput())));
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(0)));
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniformAndNotNull(plan.getOutput().get(1)));
+
+        plan = PlanChecker.from(connectContext)
+                .analyze("select * from agg except select * from uni")
+                .rewrite()
+                .getPlan();
+        
System.out.println(plan.getLogicalProperties().getFunctionalDependencies());
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(ImmutableSet.copyOf(plan.getOutput())));
+        Assertions.assertTrue(plan.getLogicalProperties()
+                
.getFunctionalDependencies().isUniqueAndNotNull(plan.getOutput().get(0)));
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/JoinEstimateTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/JoinEstimateTest.java
index 2735e26da46..282f88fc776 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/JoinEstimateTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/JoinEstimateTest.java
@@ -20,6 +20,7 @@ package org.apache.doris.nereids.stats;
 import org.apache.doris.common.IdGenerator;
 import org.apache.doris.nereids.memo.Group;
 import org.apache.doris.nereids.memo.GroupId;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.EqualTo;
 import org.apache.doris.nereids.trees.expressions.Slot;
@@ -73,14 +74,14 @@ public class JoinEstimateTest {
                     public List<Slot> get() {
                         return Lists.newArrayList(a);
                     }
-                })));
+                }, () -> FunctionalDependencies.EMPTY_FUNC_DEPS)));
         GroupPlan right = new GroupPlan(new Group(idGenerator.getNextId(), new 
LogicalProperties(
                 new Supplier<List<Slot>>() {
                     @Override
                     public List<Slot> get() {
                         return Lists.newArrayList(b);
                     }
-                })));
+                }, () -> FunctionalDependencies.EMPTY_FUNC_DEPS)));
         LogicalJoin join = new LogicalJoin(JoinType.INNER_JOIN, 
Lists.newArrayList(eq),
                 left, right);
         Statistics outputStats = JoinEstimation.estimate(leftStats, 
rightStats, join);
@@ -124,14 +125,14 @@ public class JoinEstimateTest {
                     public List<Slot> get() {
                         return Lists.newArrayList(a);
                     }
-                })));
+                }, () -> FunctionalDependencies.EMPTY_FUNC_DEPS)));
         GroupPlan right = new GroupPlan(new Group(idGenerator.getNextId(), new 
LogicalProperties(
                 new Supplier<List<Slot>>() {
                     @Override
                     public List<Slot> get() {
                         return Lists.newArrayList(b, c);
                     }
-                })));
+                }, () -> FunctionalDependencies.EMPTY_FUNC_DEPS)));
         LogicalJoin join = new LogicalJoin(JoinType.LEFT_OUTER_JOIN, 
Lists.newArrayList(eq),
                 left, right);
         Statistics outputStats = JoinEstimation.estimate(leftStats, 
rightStats, join);
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/StatsCalculatorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/StatsCalculatorTest.java
index 3dfe2d0bdf8..28591ab8cc3 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/StatsCalculatorTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/StatsCalculatorTest.java
@@ -22,6 +22,7 @@ import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.nereids.memo.Group;
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.trees.expressions.And;
 import org.apache.doris.nereids.trees.expressions.EqualTo;
@@ -61,7 +62,8 @@ public class StatsCalculatorTest {
 
     private Group newFakeGroup() {
         GroupExpression groupExpression = new GroupExpression(scan);
-        Group group = new Group(null, groupExpression, new 
LogicalProperties(Collections::emptyList));
+        Group group = new Group(null, groupExpression,
+                new LogicalProperties(Collections::emptyList, () -> 
FunctionalDependencies.EMPTY_FUNC_DEPS));
         group.getLogicalExpressions().remove(0);
         return group;
     }
@@ -250,7 +252,7 @@ public class StatsCalculatorTest {
         LogicalOlapScan logicalOlapScan1 = (LogicalOlapScan) new 
LogicalOlapScan(
                 StatementScopeIdGenerator.newRelationId(), table1,
                 
Collections.emptyList()).withGroupExprLogicalPropChildren(Optional.empty(),
-                Optional.of(new LogicalProperties(() -> 
ImmutableList.of(slot1))), ImmutableList.of());
+                Optional.of(new LogicalProperties(() -> 
ImmutableList.of(slot1), () -> FunctionalDependencies.EMPTY_FUNC_DEPS)), 
ImmutableList.of());
 
         GroupExpression groupExpression = new 
GroupExpression(logicalOlapScan1, ImmutableList.of());
         Group ownerGroup = new Group(null, groupExpression, null);


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


Reply via email to