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

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


The following commit(s) were added to refs/heads/master by this push:
     new 60846c0cdbf [FLINK-40797][table-planner] Exclude `NestedLoopJoin` from 
CBO search space for equi-joins
60846c0cdbf is described below

commit 60846c0cdbf57d5297c44f218025b189d18a2765
Author: Bonnie Varghese <[email protected]>
AuthorDate: Fri Oct 9 09:08:12 2026 -0700

    [FLINK-40797][table-planner] Exclude `NestedLoopJoin` from CBO search space 
for equi-joins
---
 .../table/planner/codegen/GenerateUtils.scala      |   1 +
 .../physical/batch/BatchPhysicalJoinRuleBase.scala |  76 +++++-
 .../sql/join/NestedLoopJoinExclusionTest.java      | 220 ++++++++++++++++++
 .../program/FlinkRuntimeFilterProgramTest.java     |   6 +-
 .../planner/plan/batch/sql/SubplanReuseTest.xml    |   6 +-
 .../batch/sql/join/NestedLoopJoinExclusionTest.xml | 255 +++++++++++++++++++++
 .../table/planner/plan/optimize/ScanReuseTest.xml  |   8 +-
 .../program/DynamicPartitionPruningProgramTest.xml |  10 +-
 .../program/FlinkRuntimeFilterProgramTest.xml      |   8 +-
 9 files changed, 565 insertions(+), 25 deletions(-)

diff --git 
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/GenerateUtils.scala
 
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/GenerateUtils.scala
index 04ea9a9c8c7..b4874396122 100644
--- 
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/GenerateUtils.scala
+++ 
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/GenerateUtils.scala
@@ -638,6 +638,7 @@ object GenerateUtils {
     case TINYINT | SMALLINT | INTEGER | BIGINT | FLOAT | DOUBLE | DATE | 
TIME_WITHOUT_TIME_ZONE |
         INTERVAL_YEAR_MONTH | INTERVAL_DAY_TIME =>
       s"($leftTerm > $rightTerm ? 1 : $leftTerm < $rightTerm ? -1 : 0)"
+    // keep in sync with BatchPhysicalJoinRuleBase#hasOrderableJoinKeys
     case TIMESTAMP_WITH_TIME_ZONE | MULTISET | MAP | VARIANT | BITMAP =>
       throw new ValidationException(
         s"Type '$t' cannot be ordered, so it cannot be used as a key for 
sorting, grouping, " +
diff --git 
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/physical/batch/BatchPhysicalJoinRuleBase.scala
 
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/physical/batch/BatchPhysicalJoinRuleBase.scala
index d895c33ff06..7b1481f04a8 100644
--- 
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/physical/batch/BatchPhysicalJoinRuleBase.scala
+++ 
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/physical/batch/BatchPhysicalJoinRuleBase.scala
@@ -19,12 +19,15 @@ package 
org.apache.flink.table.planner.plan.rules.physical.batch
 
 import org.apache.flink.table.api.{TableConfig, TableException, 
ValidationException}
 import org.apache.flink.table.api.config.OptimizerConfigOptions
+import org.apache.flink.table.planner.calcite.FlinkTypeFactory
 import org.apache.flink.table.planner.hint.{FlinkHints, JoinStrategy}
 import org.apache.flink.table.planner.plan.nodes.FlinkConventions
 import 
org.apache.flink.table.planner.plan.nodes.physical.batch.BatchPhysicalLocalHashAggregate
 import 
org.apache.flink.table.planner.plan.rules.physical.batch.BatchPhysicalJoinRuleBase.SEMI_JOIN_BUILD_DISTINCT_NDV_RATIO
 import org.apache.flink.table.planner.plan.utils.{JoinUtil, OperatorType}
 import org.apache.flink.table.planner.utils.TableConfigUtils.isOperatorDisabled
+import org.apache.flink.table.types.logical.LogicalTypeRoot._
+import org.apache.flink.table.types.logical.utils.LogicalTypeChecks.hasNested
 
 import org.apache.calcite.plan.RelOptRule
 import org.apache.calcite.rel.RelNode
@@ -153,6 +156,32 @@ trait BatchPhysicalJoinRuleBase {
     !joinInfo.pairs().isEmpty
   }
 
+  /**
+   * Whether all equi-join key columns, including nested fields, can be 
ordered. Every equi-capable
+   * strategy needs this, as the hash join also builds a sort merge fallback. 
Mirrors
+   * `GenerateUtils.generateCompare`; RAW is conservatively treated as 
unorderable.
+   *
+   * Only used to decide whether NestedLoopJoin can be excluded; the 
equi-capable strategies still
+   * do not check key types themselves.
+   */
+  private def hasOrderableJoinKeys(join: Join): Boolean = {
+    val joinInfo = join.analyzeCondition
+    val leftType = FlinkTypeFactory.toLogicalRowType(join.getLeft.getRowType)
+    val rightType = FlinkTypeFactory.toLogicalRowType(join.getRight.getRowType)
+    val keyTypes = joinInfo.leftKeys.map(i => leftType.getTypeAt(i)) ++
+      joinInfo.rightKeys.map(i => rightType.getTypeAt(i))
+    !keyTypes.exists(
+      hasNested(_, _.isAnyOf(TIMESTAMP_WITH_TIME_ZONE, MULTISET, MAP, VARIANT, 
BITMAP, RAW)))
+  }
+
+  private def hasApplicableEquivJoinStrategy(join: Join, tableConfig: 
TableConfig): Boolean = {
+    isEquivJoin(join) && hasOrderableJoinKeys(join) && (
+      checkSortMergeJoinApplicable(tableConfig) ||
+        checkShuffleHashApplicable(join, tableConfig, withShuffleHashHint = 
false)._1 ||
+        checkBroadcastApplicable(join, tableConfig, withBroadcastHint = 
false)._1
+    )
+  }
+
   /**
    * Decides whether the join can convert to BroadcastHashJoin.
    *
@@ -168,7 +197,18 @@ trait BatchPhysicalJoinRuleBase {
       tableConfig: TableConfig,
       withBroadcastHint: Boolean): (Boolean, Boolean) = {
 
-    if (!isEquivJoin(join) || isOperatorDisabled(tableConfig, 
OperatorType.BroadcastHashJoin)) {
+    if (!isEquivJoin(join)) {
+      return (false, false)
+    }
+    checkBroadcastApplicable(join, tableConfig, withBroadcastHint)
+  }
+
+  private def checkBroadcastApplicable(
+      join: Join,
+      tableConfig: TableConfig,
+      withBroadcastHint: Boolean): (Boolean, Boolean) = {
+
+    if (isOperatorDisabled(tableConfig, OperatorType.BroadcastHashJoin)) {
       return (false, false)
     }
 
@@ -235,7 +275,17 @@ trait BatchPhysicalJoinRuleBase {
       join: Join,
       tableConfig: TableConfig,
       withShuffleHashHint: Boolean): (Boolean, Boolean) = {
-    if (!isEquivJoin(join) || isOperatorDisabled(tableConfig, 
OperatorType.ShuffleHashJoin)) {
+    if (!isEquivJoin(join)) {
+      return (false, false)
+    }
+    checkShuffleHashApplicable(join, tableConfig, withShuffleHashHint)
+  }
+
+  private def checkShuffleHashApplicable(
+      join: Join,
+      tableConfig: TableConfig,
+      withShuffleHashHint: Boolean): (Boolean, Boolean) = {
+    if (isOperatorDisabled(tableConfig, OperatorType.ShuffleHashJoin)) {
       return (false, false)
     }
 
@@ -260,11 +310,11 @@ trait BatchPhysicalJoinRuleBase {
 
   // the sort merge join doesn't distinct the build side
   protected def checkSortMergeJoin(join: Join, tableConfig: TableConfig): 
Boolean = {
-    if (!isEquivJoin(join) || isOperatorDisabled(tableConfig, 
OperatorType.SortMergeJoin)) {
-      false
-    } else {
-      true
-    }
+    isEquivJoin(join) && checkSortMergeJoinApplicable(tableConfig)
+  }
+
+  private def checkSortMergeJoinApplicable(tableConfig: TableConfig): Boolean 
= {
+    !isOperatorDisabled(tableConfig, OperatorType.SortMergeJoin)
   }
 
   protected def checkNestLoopJoin(
@@ -276,6 +326,10 @@ trait BatchPhysicalJoinRuleBase {
       return (false, false)
     }
 
+    if (!withNestLoopHint && hasApplicableEquivJoinStrategy(join, 
tableConfig)) {
+      return (false, false)
+    }
+
     val isLeftToBuild = if (withNestLoopHint) {
       getFirstArgInJoinHint(join, JoinStrategy.NEST_LOOP.getJoinHintName)
         .equals(FlinkHints.LEFT_INPUT)
@@ -297,7 +351,13 @@ trait BatchPhysicalJoinRuleBase {
 
     }
 
-    // all join can use NEST LOOP JOIN
+    // reached only for:
+    // 1. non-equi joins
+    // 2. equi-joins with an explicit NEST_LOOP hint
+    // 3. equi-joins where no equi-capable strategy is applicable (e.g. 
ShuffleHashJoin/SortMergeJoin
+    // disabled and BroadcastHashJoin inapplicable, due to unknown or 
oversized inputs)
+    // 4. equi-joins on keys that cannot be ordered (e.g. MAP), which no 
equi-capable strategy
+    // supports
     (true, isLeftToBuild)
 
   }
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/batch/sql/join/NestedLoopJoinExclusionTest.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/batch/sql/join/NestedLoopJoinExclusionTest.java
new file mode 100644
index 00000000000..14e1f391f48
--- /dev/null
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/batch/sql/join/NestedLoopJoinExclusionTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.table.planner.plan.batch.sql.join;
+
+import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
+import org.apache.flink.api.common.typeinfo.PrimitiveArrayTypeInfo;
+import org.apache.flink.api.common.typeinfo.TypeInformation;
+import org.apache.flink.api.java.typeutils.MapTypeInfo;
+import org.apache.flink.api.java.typeutils.MultisetTypeInfo;
+import org.apache.flink.api.java.typeutils.ObjectArrayTypeInfo;
+import org.apache.flink.api.java.typeutils.RowTypeInfo;
+import org.apache.flink.table.api.TableConfig;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.plan.stats.TableStats;
+import org.apache.flink.table.planner.plan.stats.FlinkStatistic;
+import org.apache.flink.table.planner.utils.BatchTableTestUtil;
+import org.apache.flink.table.planner.utils.TableTestBase;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Plan tests for excluding {@code NestedLoopJoin} from the CBO's candidate 
set for equi-joins,
+ * added by {@link
+ * 
org.apache.flink.table.planner.plan.rules.physical.batch.BatchPhysicalJoinRuleBase#checkNestLoopJoin}.
+ *
+ * <p>For an equi-join, nested-loop join must never be picked over an 
equi-capable strategy, even
+ * when the cost-based cardinality estimate makes it look cheap.
+ */
+public class NestedLoopJoinExclusionTest extends TableTestBase {
+
+    private BatchTableTestUtil util;
+
+    private static final TypeInformation<?> INT_INFO = 
BasicTypeInfo.INT_TYPE_INFO;
+    private static final TypeInformation<?> MAP_INFO = new 
MapTypeInfo<>(INT_INFO, INT_INFO);
+
+    @BeforeEach
+    void setup() {
+        util = batchTestUtil(TableConfig.getDefault());
+    }
+
+    @Test
+    void testEquiJoinWithSkewedCardinalityDoesNotUseNestedLoopJoin() {
+        // Left side's estimated row count is tiny relative to the right side.
+        // NestedLoopJoin's L*R cost (~2M) looks cheaper than HashJoin's 
8*(L+R) cost (~8M),
+        // even though the join condition is a plain equality.
+        util.addTableSource(
+                "TinySide",
+                new TypeInformation<?>[] {INT_INFO, INT_INFO},
+                new String[] {"a", "b"},
+                FlinkStatistic.builder().tableStats(new 
TableStats(2L)).build());
+        util.addTableSource(
+                "HugeSide",
+                new TypeInformation<?>[] {INT_INFO, INT_INFO},
+                new String[] {"d", "e"},
+                FlinkStatistic.builder().tableStats(new 
TableStats(1_000_000L)).build());
+
+        util.verifyRelPlan("SELECT a, d FROM TinySide, HugeSide WHERE a = d");
+    }
+
+    @Test
+    void testEquiJoinOnComplexKeyStillProducesValidPlan() {
+        // An equi-join on a complex (ARRAY) key still yields a valid plan via 
an
+        // equi-capable strategy rather than being stranded with no candidate 
at all.
+        TypeInformation<?> arrayInfo = 
PrimitiveArrayTypeInfo.INT_PRIMITIVE_ARRAY_TYPE_INFO;
+        util.addTableSource(
+                "ArrLeft",
+                new TypeInformation<?>[] {INT_INFO, arrayInfo},
+                new String[] {"a", "r"},
+                FlinkStatistic.UNKNOWN());
+        util.addTableSource(
+                "ArrRight",
+                new TypeInformation<?>[] {INT_INFO, arrayInfo},
+                new String[] {"d", "s"},
+                FlinkStatistic.UNKNOWN());
+
+        util.verifyRelPlan("SELECT a, d FROM ArrLeft, ArrRight WHERE r = s");
+    }
+
+    @Test
+    void 
testEquiJoinFallsBackToNestedLoopJoinWhenOnlyInapplicableBroadcastIsEnabled() {
+        // ShuffleHashJoin/SortMergeJoin disabled; BroadcastHashJoin stays 
enabled but cannot
+        // apply since both sides have unknown size stats. NestedLoopJoin must 
remain available
+        // as a fallback here, otherwise the equi-join is left with no valid 
physical strategy.
+        util.tableEnv()
+                .getConfig()
+                .set(
+                        ExecutionConfigOptions.TABLE_EXEC_DISABLED_OPERATORS,
+                        "ShuffleHashJoin, SortMergeJoin");
+        util.addTableSource(
+                "LeftUnknown",
+                new TypeInformation<?>[] {INT_INFO, INT_INFO},
+                new String[] {"a", "b"},
+                FlinkStatistic.UNKNOWN());
+        util.addTableSource(
+                "RightUnknown",
+                new TypeInformation<?>[] {INT_INFO, INT_INFO},
+                new String[] {"d", "e"},
+                FlinkStatistic.UNKNOWN());
+
+        util.verifyRelPlan("SELECT a, d FROM LeftUnknown, RightUnknown WHERE a 
= d");
+    }
+
+    @Test
+    void testEquiJoinStillAvoidsNestedLoopJoinWhenBroadcastIsApplicable() {
+        // Same skewed-cardinality setup as
+        // testEquiJoinWithSkewedCardinalityDoesNotUseNestedLoopJoin, but with
+        // ShuffleHashJoin/SortMergeJoin disabled so only BroadcastHashJoin is 
applicable
+        util.tableEnv()
+                .getConfig()
+                .set(
+                        ExecutionConfigOptions.TABLE_EXEC_DISABLED_OPERATORS,
+                        "ShuffleHashJoin, SortMergeJoin");
+        util.addTableSource(
+                "TinySide",
+                new TypeInformation<?>[] {INT_INFO, INT_INFO},
+                new String[] {"a", "b"},
+                FlinkStatistic.builder().tableStats(new 
TableStats(2L)).build());
+        util.addTableSource(
+                "HugeSide",
+                new TypeInformation<?>[] {INT_INFO, INT_INFO},
+                new String[] {"d", "e"},
+                FlinkStatistic.builder().tableStats(new 
TableStats(1_000_000L)).build());
+
+        util.verifyRelPlan("SELECT a, d FROM TinySide, HugeSide WHERE a = d");
+    }
+
+    @Test
+    void testEquiJoinOnMapKeyKeepsNestedLoopJoinWhenHashJoinsAreDisabled() {
+        // MAP cannot be ordered, so SortMergeJoin cannot handle it either. 
NestedLoopJoin is the
+        // only strategy that can run, so it must not be excluded.
+        util.tableEnv()
+                .getConfig()
+                .set(
+                        ExecutionConfigOptions.TABLE_EXEC_DISABLED_OPERATORS,
+                        "ShuffleHashJoin, BroadcastHashJoin");
+        addSkewedTables(MAP_INFO);
+
+        util.verifyRelPlan("SELECT a, d FROM TinySide, HugeSide WHERE m = n");
+    }
+
+    @Test
+    void testEquiJoinOnMapKeyKeepsNestedLoopJoinWhenAllStrategiesAreEnabled() {
+        // The hash joins need an orderable key too (they build a sort merge 
fallback).
+        addSkewedTables(MAP_INFO);
+
+        util.verifyRelPlan("SELECT a, d FROM TinySide, HugeSide WHERE m = n");
+    }
+
+    @Test
+    void testEquiJoinOnMultisetKeyKeepsNestedLoopJoin() {
+        addSkewedTables(new MultisetTypeInfo<>(INT_INFO));
+
+        util.verifyRelPlan("SELECT a, d FROM TinySide, HugeSide WHERE m = n");
+    }
+
+    @Test
+    void testEquiJoinOnNestedMapKeyKeepsNestedLoopJoin() {
+        // The unorderable type is nested inside ARRAY and ROW.
+        addSkewedTables(
+                ObjectArrayTypeInfo.getInfoFor(
+                        new RowTypeInfo(new TypeInformation<?>[] {MAP_INFO}, 
new String[] {"f"})));
+
+        util.verifyRelPlan("SELECT a, d FROM TinySide, HugeSide WHERE m = n");
+    }
+
+    @Test
+    void testEquiJoinOnCompositeKeyWithMapKeepsNestedLoopJoin() {
+        // One unorderable key column is enough: the orderable column does not 
rescue the join.
+        util.addTableSource(
+                "TinySide",
+                new TypeInformation<?>[] {INT_INFO, INT_INFO, MAP_INFO},
+                new String[] {"a", "b", "m"},
+                FlinkStatistic.builder().tableStats(new 
TableStats(2L)).build());
+        util.addTableSource(
+                "HugeSide",
+                new TypeInformation<?>[] {INT_INFO, INT_INFO, MAP_INFO},
+                new String[] {"d", "e", "n"},
+                FlinkStatistic.builder().tableStats(new 
TableStats(1_000_000L)).build());
+
+        util.verifyRelPlan("SELECT a, d FROM TinySide, HugeSide WHERE a = d 
AND m = n");
+    }
+
+    @Test
+    void testEquiJoinWithMapNonKeyColumnStillAvoidsNestedLoopJoin() {
+        // Only the equi-key columns matter; a MAP payload column must not 
block a hash join.
+        addSkewedTables(MAP_INFO);
+
+        util.verifyRelPlan("SELECT a, d, m, n FROM TinySide, HugeSide WHERE a 
= d");
+    }
+
+    private void addSkewedTables(TypeInformation<?> mapColumnType) {
+        util.addTableSource(
+                "TinySide",
+                new TypeInformation<?>[] {INT_INFO, mapColumnType},
+                new String[] {"a", "m"},
+                FlinkStatistic.builder().tableStats(new 
TableStats(2L)).build());
+        util.addTableSource(
+                "HugeSide",
+                new TypeInformation<?>[] {INT_INFO, mapColumnType},
+                new String[] {"d", "n"},
+                FlinkStatistic.builder().tableStats(new 
TableStats(1_000_000L)).build());
+    }
+}
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/optimize/program/FlinkRuntimeFilterProgramTest.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/optimize/program/FlinkRuntimeFilterProgramTest.java
index 0031885a858..dec87fcbe35 100644
--- 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/optimize/program/FlinkRuntimeFilterProgramTest.java
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/optimize/program/FlinkRuntimeFilterProgramTest.java
@@ -152,7 +152,11 @@ public class FlinkRuntimeFilterProgramTest extends 
TableTestBase {
         // runtime filter will not succeed
         setupTableRowCount("dim", 1L);
         setupTableRowCount("fact", SUITABLE_FACT_ROW_COUNT);
-        String query = "select * from fact, dim where fact.amount = dim.amount 
and dim.price < 500";
+        // force nested loop join explicitly using a hint
+        // with an equi-condition, the CBO now prefers a hash join over nested 
loop join
+        String query =
+                "SELECT /*+ NEST_LOOP(dim) */ * FROM fact, dim"
+                        + " WHERE fact.amount = dim.amount AND dim.price < 
500";
         util.verifyPlan(query);
     }
 
diff --git 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/SubplanReuseTest.xml
 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/SubplanReuseTest.xml
index 2120efbd43b..411e16c1095 100644
--- 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/SubplanReuseTest.xml
+++ 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/SubplanReuseTest.xml
@@ -1174,7 +1174,7 @@ LogicalProject(c=[$0], a=[$1], b=[$2], c0=[$3], a0=[$4], 
b0=[$5])
     </Resource>
     <Resource name="optimized exec plan">
       <![CDATA[
-NestedLoopJoin(joinType=[InnerJoin], where=[(a = a0)], select=[c, a, b, c0, 
a0, b0], build=[right])
+HashJoin(joinType=[InnerJoin], where=[(a = a0)], select=[c, a, b, c0, a0, b0], 
isBroadcast=[true], build=[right])
 :- Exchange(distribution=[any], shuffle_mode=[BATCH])
 :  +- Calc(select=[c, a, b], where=[(a > 1)])
 :     +- SortLimit(orderBy=[a ASC, b DESC], offset=[0], fetch=[10], 
global=[true])(reuse_id=[1])
@@ -1362,7 +1362,7 @@ LogicalIntersect(all=[false])
     </Resource>
     <Resource name="optimized exec plan">
       <![CDATA[
-MultipleInput(readOrder=[0,1], 
members=[\nNestedLoopJoin(joinType=[LeftSemiJoin], where=[IS NOT DISTINCT 
FROM(random, random0)], select=[random], build=[right])\n:- 
SortAggregate(isMerge=[false], groupBy=[random], select=[random])\n:  +- 
Sort(orderBy=[random ASC])\n:     +- [#2] 
Exchange(distribution=[hash[random]])\n+- [#1] 
Exchange(distribution=[broadcast])\n])
+MultipleInput(readOrder=[0,1], members=[\nHashJoin(joinType=[LeftSemiJoin], 
where=[IS NOT DISTINCT FROM(random, random0)], select=[random], 
isBroadcast=[true], build=[right])\n:- SortAggregate(isMerge=[false], 
groupBy=[random], select=[random])\n:  +- Sort(orderBy=[random ASC])\n:     +- 
[#2] Exchange(distribution=[hash[random]])\n+- [#1] 
Exchange(distribution=[broadcast])\n])
 :- Exchange(distribution=[broadcast])(reuse_id=[2])
 :  +- Calc(select=[random])(reuse_id=[1])
 :     +- SortLimit(orderBy=[EXPR$1 ASC], offset=[0], fetch=[1], global=[true])
@@ -1371,7 +1371,7 @@ MultipleInput(readOrder=[0,1], 
members=[\nNestedLoopJoin(joinType=[LeftSemiJoin]
 :              +- Calc(select=[a AS random, RAND() AS EXPR$1])
 :                 +- TableSourceScan(table=[[default_catalog, 
default_database, x]], fields=[a, b, c])
 +- Exchange(distribution=[hash[random]])
-   +- NestedLoopJoin(joinType=[LeftSemiJoin], where=[IS NOT DISTINCT 
FROM(random, random0)], select=[random], build=[right])
+   +- HashJoin(joinType=[LeftSemiJoin], where=[IS NOT DISTINCT FROM(random, 
random0)], select=[random], isBroadcast=[true], build=[right])
       :- Exchange(distribution=[any], shuffle_mode=[BATCH])
       :  +- Reused(reference_id=[1])
       +- Reused(reference_id=[2])
diff --git 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/join/NestedLoopJoinExclusionTest.xml
 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/join/NestedLoopJoinExclusionTest.xml
new file mode 100644
index 00000000000..d1986272b94
--- /dev/null
+++ 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/join/NestedLoopJoinExclusionTest.xml
@@ -0,0 +1,255 @@
+<?xml version="1.0" ?>
+<!--
+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.
+-->
+<Root>
+  <TestCase 
name="testEquiJoinFallsBackToNestedLoopJoinWhenOnlyInapplicableBroadcastIsEnabled">
+    <Resource name="sql">
+      <![CDATA[SELECT a, d FROM LeftUnknown, RightUnknown WHERE a = d]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], d=[$2])
++- LogicalFilter(condition=[=($0, $2)])
+   +- LogicalJoin(condition=[true], joinType=[inner])
+      :- LogicalTableScan(table=[[default_catalog, default_database, 
LeftUnknown, source: [TestTableSource(a, b)]]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, 
RightUnknown, source: [TestTableSource(d, e)]]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+NestedLoopJoin(joinType=[InnerJoin], where=[=(a, d)], select=[a, d], 
build=[left])
+:- Exchange(distribution=[broadcast])
+:  +- Calc(select=[a])
+:     +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
LeftUnknown, source: [TestTableSource(a, b)]]], fields=[a, b])
++- Calc(select=[d])
+   +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
RightUnknown, source: [TestTableSource(d, e)]]], fields=[d, e])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEquiJoinOnComplexKeyStillProducesValidPlan">
+    <Resource name="sql">
+      <![CDATA[SELECT a, d FROM ArrLeft, ArrRight WHERE r = s]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], d=[$2])
++- LogicalFilter(condition=[=($1, $3)])
+   +- LogicalJoin(condition=[true], joinType=[inner])
+      :- LogicalTableScan(table=[[default_catalog, default_database, ArrLeft, 
source: [TestTableSource(a, r)]]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, ArrRight, 
source: [TestTableSource(d, s)]]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+Calc(select=[a, d])
++- HashJoin(joinType=[InnerJoin], where=[=(r, s)], select=[a, r, d, s], 
build=[left])
+   :- Exchange(distribution=[hash[r]])
+   :  +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
ArrLeft, source: [TestTableSource(a, r)]]], fields=[a, r])
+   +- Exchange(distribution=[hash[s]])
+      +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
ArrRight, source: [TestTableSource(d, s)]]], fields=[d, s])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEquiJoinOnCompositeKeyWithMapKeepsNestedLoopJoin">
+    <Resource name="sql">
+      <![CDATA[SELECT a, d FROM TinySide, HugeSide WHERE a = d AND m = n]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], d=[$3])
++- LogicalFilter(condition=[AND(=($0, $3), =($2, $5))])
+   +- LogicalJoin(condition=[true], joinType=[inner])
+      :- LogicalTableScan(table=[[default_catalog, default_database, TinySide, 
source: [TestTableSource(a, b, m)]]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, HugeSide, 
source: [TestTableSource(d, e, n)]]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+Calc(select=[a, d])
++- NestedLoopJoin(joinType=[InnerJoin], where=[AND(=(a, d), =(m, n))], 
select=[a, m, d, n], build=[left])
+   :- Exchange(distribution=[broadcast])
+   :  +- Calc(select=[a, m])
+   :     +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
TinySide, source: [TestTableSource(a, b, m)]]], fields=[a, b, m])
+   +- Calc(select=[d, n])
+      +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
HugeSide, source: [TestTableSource(d, e, n)]]], fields=[d, e, n])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase 
name="testEquiJoinOnMapKeyKeepsNestedLoopJoinWhenAllStrategiesAreEnabled">
+    <Resource name="sql">
+      <![CDATA[SELECT a, d FROM TinySide, HugeSide WHERE m = n]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], d=[$2])
++- LogicalFilter(condition=[=($1, $3)])
+   +- LogicalJoin(condition=[true], joinType=[inner])
+      :- LogicalTableScan(table=[[default_catalog, default_database, TinySide, 
source: [TestTableSource(a, m)]]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, HugeSide, 
source: [TestTableSource(d, n)]]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+Calc(select=[a, d])
++- NestedLoopJoin(joinType=[InnerJoin], where=[=(m, n)], select=[a, m, d, n], 
build=[left])
+   :- Exchange(distribution=[broadcast])
+   :  +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
TinySide, source: [TestTableSource(a, m)]]], fields=[a, m])
+   +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
HugeSide, source: [TestTableSource(d, n)]]], fields=[d, n])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase 
name="testEquiJoinOnMapKeyKeepsNestedLoopJoinWhenHashJoinsAreDisabled">
+    <Resource name="sql">
+      <![CDATA[SELECT a, d FROM TinySide, HugeSide WHERE m = n]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], d=[$2])
++- LogicalFilter(condition=[=($1, $3)])
+   +- LogicalJoin(condition=[true], joinType=[inner])
+      :- LogicalTableScan(table=[[default_catalog, default_database, TinySide, 
source: [TestTableSource(a, m)]]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, HugeSide, 
source: [TestTableSource(d, n)]]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+Calc(select=[a, d])
++- NestedLoopJoin(joinType=[InnerJoin], where=[=(m, n)], select=[a, m, d, n], 
build=[left])
+   :- Exchange(distribution=[broadcast])
+   :  +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
TinySide, source: [TestTableSource(a, m)]]], fields=[a, m])
+   +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
HugeSide, source: [TestTableSource(d, n)]]], fields=[d, n])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEquiJoinOnMultisetKeyKeepsNestedLoopJoin">
+    <Resource name="sql">
+      <![CDATA[SELECT a, d FROM TinySide, HugeSide WHERE m = n]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], d=[$2])
++- LogicalFilter(condition=[=($1, $3)])
+   +- LogicalJoin(condition=[true], joinType=[inner])
+      :- LogicalTableScan(table=[[default_catalog, default_database, TinySide, 
source: [TestTableSource(a, m)]]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, HugeSide, 
source: [TestTableSource(d, n)]]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+Calc(select=[a, d])
++- NestedLoopJoin(joinType=[InnerJoin], where=[=(m, n)], select=[a, m, d, n], 
build=[left])
+   :- Exchange(distribution=[broadcast])
+   :  +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
TinySide, source: [TestTableSource(a, m)]]], fields=[a, m])
+   +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
HugeSide, source: [TestTableSource(d, n)]]], fields=[d, n])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEquiJoinOnNestedMapKeyKeepsNestedLoopJoin">
+    <Resource name="sql">
+      <![CDATA[SELECT a, d FROM TinySide, HugeSide WHERE m = n]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], d=[$2])
++- LogicalFilter(condition=[=($1, $3)])
+   +- LogicalJoin(condition=[true], joinType=[inner])
+      :- LogicalTableScan(table=[[default_catalog, default_database, TinySide, 
source: [TestTableSource(a, m)]]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, HugeSide, 
source: [TestTableSource(d, n)]]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+Calc(select=[a, d])
++- NestedLoopJoin(joinType=[InnerJoin], where=[=(m, n)], select=[a, m, d, n], 
build=[left])
+   :- Exchange(distribution=[broadcast])
+   :  +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
TinySide, source: [TestTableSource(a, m)]]], fields=[a, m])
+   +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
HugeSide, source: [TestTableSource(d, n)]]], fields=[d, n])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase 
name="testEquiJoinStillAvoidsNestedLoopJoinWhenBroadcastIsApplicable">
+    <Resource name="sql">
+      <![CDATA[SELECT a, d FROM TinySide, HugeSide WHERE a = d]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], d=[$2])
++- LogicalFilter(condition=[=($0, $2)])
+   +- LogicalJoin(condition=[true], joinType=[inner])
+      :- LogicalTableScan(table=[[default_catalog, default_database, TinySide, 
source: [TestTableSource(a, b)]]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, HugeSide, 
source: [TestTableSource(d, e)]]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+HashJoin(joinType=[InnerJoin], where=[=(a, d)], select=[a, d], 
isBroadcast=[true], build=[left])
+:- Exchange(distribution=[broadcast])
+:  +- Calc(select=[a])
+:     +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
TinySide, source: [TestTableSource(a, b)]]], fields=[a, b])
++- Calc(select=[d])
+   +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
HugeSide, source: [TestTableSource(d, e)]]], fields=[d, e])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEquiJoinWithMapNonKeyColumnStillAvoidsNestedLoopJoin">
+    <Resource name="sql">
+      <![CDATA[SELECT a, d, m, n FROM TinySide, HugeSide WHERE a = d]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], d=[$2], m=[$1], n=[$3])
++- LogicalFilter(condition=[=($0, $2)])
+   +- LogicalJoin(condition=[true], joinType=[inner])
+      :- LogicalTableScan(table=[[default_catalog, default_database, TinySide, 
source: [TestTableSource(a, m)]]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, HugeSide, 
source: [TestTableSource(d, n)]]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+Calc(select=[a, d, m, n])
++- HashJoin(joinType=[InnerJoin], where=[=(a, d)], select=[a, m, d, n], 
isBroadcast=[true], build=[left])
+   :- Exchange(distribution=[broadcast])
+   :  +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
TinySide, source: [TestTableSource(a, m)]]], fields=[a, m])
+   +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
HugeSide, source: [TestTableSource(d, n)]]], fields=[d, n])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEquiJoinWithSkewedCardinalityDoesNotUseNestedLoopJoin">
+    <Resource name="sql">
+      <![CDATA[SELECT a, d FROM TinySide, HugeSide WHERE a = d]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], d=[$2])
++- LogicalFilter(condition=[=($0, $2)])
+   +- LogicalJoin(condition=[true], joinType=[inner])
+      :- LogicalTableScan(table=[[default_catalog, default_database, TinySide, 
source: [TestTableSource(a, b)]]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, HugeSide, 
source: [TestTableSource(d, e)]]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+HashJoin(joinType=[InnerJoin], where=[=(a, d)], select=[a, d], 
isBroadcast=[true], build=[left])
+:- Exchange(distribution=[broadcast])
+:  +- Calc(select=[a])
+:     +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
TinySide, source: [TestTableSource(a, b)]]], fields=[a, b])
++- Calc(select=[d])
+   +- LegacyTableSourceScan(table=[[default_catalog, default_database, 
HugeSide, source: [TestTableSource(d, e)]]], fields=[d, e])
+]]>
+    </Resource>
+  </TestCase>
+</Root>
diff --git 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/ScanReuseTest.xml
 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/ScanReuseTest.xml
index 8126119b1a6..155081bd8bf 100644
--- 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/ScanReuseTest.xml
+++ 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/ScanReuseTest.xml
@@ -449,14 +449,14 @@ LogicalProject(a=[$0], c=[$2], c0=[$9])
     <Resource name="optimized exec plan">
       <![CDATA[
 Calc(select=[a, c, c0])
-+- NestedLoopJoin(joinType=[InnerJoin], where=[(a = a0)], select=[a, c, a0, 
c0], build=[left])
-   :- Exchange(distribution=[broadcast])
++- HashJoin(joinType=[InnerJoin], where=[(a = a0)], select=[a, c, a0, c0], 
isBroadcast=[true], build=[right])
+   :- Exchange(distribution=[any], shuffle_mode=[BATCH])
    :  +- Calc(select=[a, c])(reuse_id=[1])
    :     +- Limit(offset=[0], fetch=[10], global=[true])
    :        +- Exchange(distribution=[single])
    :           +- Limit(offset=[0], fetch=[10], global=[false])
    :              +- TableSourceScan(table=[[default_catalog, 
default_database, MyTable, limit=[10]]], fields=[a, b, c, nested, metadata_1, 
metadata_2])
-   +- Exchange(distribution=[any], shuffle_mode=[BATCH])
+   +- Exchange(distribution=[broadcast])
       +- Reused(reference_id=[1])
 ]]>
     </Resource>
@@ -888,7 +888,7 @@ LogicalProject(a=[$0], c=[$2], c0=[$9])
     <Resource name="optimized exec plan">
       <![CDATA[
 Calc(select=[a, c, c0])
-+- NestedLoopJoin(joinType=[InnerJoin], where=[(a = a0)], select=[a, c, a0, 
c0], build=[right])
++- HashJoin(joinType=[InnerJoin], where=[(a = a0)], select=[a, c, a0, c0], 
isBroadcast=[true], build=[right])
    :- Calc(select=[a, c])
    :  +- Limit(offset=[0], fetch=[11], global=[true])
    :     +- Exchange(distribution=[single])
diff --git 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/program/DynamicPartitionPruningProgramTest.xml
 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/program/DynamicPartitionPruningProgramTest.xml
index c41077ca0dc..9e288d37bf5 100644
--- 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/program/DynamicPartitionPruningProgramTest.xml
+++ 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/program/DynamicPartitionPruningProgramTest.xml
@@ -716,7 +716,7 @@ LogicalProject(id=[$0], name=[$1], amount=[$2], price=[$3], 
fact_date_sk=[$4], i
     </Resource>
     <Resource name="optimized rel plan">
       <![CDATA[
-NestedLoopJoin(joinType=[InnerJoin], where=[=(fact_date_sk, dim_date_sk)], 
select=[id, name, amount, price, fact_date_sk, id0, male, amount0, price0, 
dim_date_sk], build=[right])
+HashJoin(joinType=[InnerJoin], where=[=(fact_date_sk, dim_date_sk)], 
select=[id, name, amount, price, fact_date_sk, id0, male, amount0, price0, 
dim_date_sk], isBroadcast=[true], build=[right])
 :- DynamicFilteringTableSourceScan(table=[[testCatalog, test_database, 
fact_part]], fields=[id, name, amount, price, fact_date_sk])
 :  +- DynamicFilteringDataCollector(fields=[dim_date_sk])
 :     +- Calc(select=[id, male, amount, price, dim_date_sk], where=[<(price, 
500)])
@@ -742,7 +742,7 @@ LogicalProject(id=[$0], name=[$1], amount=[$2], price=[$3], 
fact_date_sk=[$4], i
     </Resource>
     <Resource name="optimized rel plan">
       <![CDATA[
-NestedLoopJoin(joinType=[InnerJoin], where=[=(fact_date_sk, dim_date_sk)], 
select=[id, name, amount, price, fact_date_sk, id0, male, amount0, price0, 
dim_date_sk], build=[right])
+HashJoin(joinType=[InnerJoin], where=[=(fact_date_sk, dim_date_sk)], 
select=[id, name, amount, price, fact_date_sk, id0, male, amount0, price0, 
dim_date_sk], isBroadcast=[true], build=[right])
 :- Calc(select=[id, name, amount, price, fact_date_sk], where=[>(price, 200)])
 :  +- DynamicFilteringTableSourceScan(table=[[testCatalog, test_database, 
fact_part, filter=[]]], fields=[id, name, amount, price, fact_date_sk])
 :     +- DynamicFilteringDataCollector(fields=[dim_date_sk])
@@ -824,7 +824,7 @@ LogicalProject(id=[$0], male=[$1], amount=[$2], price=[$3], 
dim_date_sk=[$4], id
     </Resource>
     <Resource name="optimized rel plan">
       <![CDATA[
-NestedLoopJoin(joinType=[InnerJoin], where=[=(fact_date_sk, dim_date_sk)], 
select=[id, male, amount, price, dim_date_sk, id0, name, amount0, price0, 
fact_date_sk], build=[left])
+HashJoin(joinType=[InnerJoin], where=[=(fact_date_sk, dim_date_sk)], 
select=[id, male, amount, price, dim_date_sk, id0, name, amount0, price0, 
fact_date_sk], isBroadcast=[true], build=[left])
 :- Exchange(distribution=[broadcast])
 :  +- Calc(select=[id, male, amount, price, dim_date_sk], where=[<(price, 
500)])
 :     +- TableSourceScan(table=[[testCatalog, test_database, dim, filter=[]]], 
fields=[id, male, amount, price, dim_date_sk])
@@ -850,7 +850,7 @@ LogicalProject(id=[$0], male=[$1], amount=[$2], price=[$3], 
dim_date_sk=[$4], id
     </Resource>
     <Resource name="optimized rel plan">
       <![CDATA[
-NestedLoopJoin(joinType=[InnerJoin], where=[=(fact_date_sk, dim_date_sk)], 
select=[id, male, amount, price, dim_date_sk, id0, name, amount0, price0, 
fact_date_sk], build=[left])
+HashJoin(joinType=[InnerJoin], where=[=(fact_date_sk, dim_date_sk)], 
select=[id, male, amount, price, dim_date_sk, id0, name, amount0, price0, 
fact_date_sk], isBroadcast=[true], build=[left])
 :- Exchange(distribution=[broadcast])
 :  +- Calc(select=[id, male, amount, price, dim_date_sk], where=[<(price, 
500)])
 :     +- TableSourceScan(table=[[testCatalog, test_database, dim, filter=[]]], 
fields=[id, male, amount, price, dim_date_sk])
@@ -933,7 +933,7 @@ LogicalProject(id=[$0], male=[$1], amount=[$2], price=[$3], 
dim_date_sk=[$4], fa
     </Resource>
     <Resource name="optimized rel plan">
       <![CDATA[
-NestedLoopJoin(joinType=[InnerJoin], where=[AND(=(fact_date_sk, dim_date_sk), 
=(amount0, amount))], select=[id, male, amount, price, dim_date_sk, 
fact_date_sk, amount0], build=[left])
+HashJoin(joinType=[InnerJoin], where=[AND(=(fact_date_sk, dim_date_sk), 
=(amount0, amount))], select=[id, male, amount, price, dim_date_sk, 
fact_date_sk, amount0], isBroadcast=[true], build=[left])
 :- Exchange(distribution=[broadcast])
 :  +- Calc(select=[id, male, amount, price, dim_date_sk], where=[<(price, 
500)])
 :     +- TableSourceScan(table=[[testCatalog, test_database, dim, filter=[]]], 
fields=[id, male, amount, price, dim_date_sk])
diff --git 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/program/FlinkRuntimeFilterProgramTest.xml
 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/program/FlinkRuntimeFilterProgramTest.xml
index 22749949dc4..148836f6870 100644
--- 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/program/FlinkRuntimeFilterProgramTest.xml
+++ 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/optimize/program/FlinkRuntimeFilterProgramTest.xml
@@ -757,15 +757,15 @@ HashJoin(joinType=[LeftOuterJoin], where=[(amount = 
amount0)], select=[id, name,
   </TestCase>
   <TestCase name="testNestedLoopJoin">
     <Resource name="sql">
-      <![CDATA[select * from fact, dim where fact.amount = dim.amount and 
dim.price < 500]]>
+      <![CDATA[SELECT /*+ NEST_LOOP(dim) */ * FROM fact, dim WHERE fact.amount 
= dim.amount AND dim.price < 500]]>
     </Resource>
     <Resource name="ast">
       <![CDATA[
 LogicalProject(id=[$0], name=[$1], amount=[$2], price=[$3], fact_date_sk=[$4], 
id0=[$5], male=[$6], amount0=[$7], price0=[$8], dim_date_sk=[$9])
 +- LogicalFilter(condition=[AND(=($2, $7), <($8, 500))])
-   +- LogicalJoin(condition=[true], joinType=[inner])
-      :- LogicalTableScan(table=[[testCatalog, test_database, fact]])
-      +- LogicalTableScan(table=[[testCatalog, test_database, dim]])
+   +- LogicalJoin(condition=[true], joinType=[inner], joinHints=[[[NEST_LOOP 
inheritPath:[0, 0] options:[dim]]]])
+      :- LogicalTableScan(table=[[testCatalog, test_database, fact]], 
hints=[[[ALIAS inheritPath:[] options:[fact]]]])
+      +- LogicalTableScan(table=[[testCatalog, test_database, dim]], 
hints=[[[ALIAS inheritPath:[] options:[dim]]]])
 ]]>
     </Resource>
     <Resource name="optimized rel plan">

Reply via email to