You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@flink.apache.org by GitBox <gi...@apache.org> on 2022/12/19 14:34:20 UTC

[GitHub] [flink] swuferhong opened a new pull request, #21530: [FLINK-30376][table-planner] Introduce a new flink busy join reorder rule which based on greedy algorithm

swuferhong opened a new pull request, #21530:
URL: https://github.com/apache/flink/pull/21530

   <!--
   *Thank you very much for contributing to Apache Flink - we are happy that you want to help us improve Flink. To help the community review your contribution in the best possible way, please go through the checklist below, which will get the contribution into a shape in which it can be best reviewed.*
   
   *Please understand that we do not do this to make contributions to Flink a hassle. In order to uphold a high standard of quality for code contributions, while at the same time managing a large number of contributions, we need contributors to prepare the contributions well, and give reviewers enough contextual information for the review. Please also understand that contributions that do not follow this guide will take longer to review and thus typically be picked up with lower priority by the community.*
   
   ## Contribution Checklist
   
     - Make sure that the pull request corresponds to a [JIRA issue](https://issues.apache.org/jira/projects/FLINK/issues). Exceptions are made for typos in JavaDoc or documentation files, which need no JIRA issue.
     
     - Name the pull request in the form "[FLINK-XXXX] [component] Title of the pull request", where *FLINK-XXXX* should be replaced by the actual issue number. Skip *component* if you are unsure about which is the best component.
     Typo fixes that have no associated JIRA issue should be named following this pattern: `[hotfix] [docs] Fix typo in event time introduction` or `[hotfix] [javadocs] Expand JavaDoc for PuncuatedWatermarkGenerator`.
   
     - Fill out the template below to describe the changes contributed by the pull request. That will give reviewers the context they need to do the review.
     
     - Make sure that the change passes the automated tests, i.e., `mvn clean verify` passes. You can set up Azure Pipelines CI to do that following [this guide](https://cwiki.apache.org/confluence/display/FLINK/Azure+Pipelines#AzurePipelines-Tutorial:SettingupAzurePipelinesforaforkoftheFlinkrepository).
   
     - Each pull request should address only one issue, not mix up code from multiple issues.
     
     - Each commit in the pull request has a meaningful commit message (including the JIRA id)
   
     - Once all items of the checklist are addressed, remove the above text and this checklist, leaving only the filled out template below.
   
   
   **(The sections below can be removed for hotfixes of typos)**
   -->
   
   ## What is the purpose of the change
   
   Introducing a new Flink busy join reorder strategy which based on the greedy algorithm. The old join reorder rule will also be the default join reorder rule and the new busy join reorder strategy will be optional.
   
   
   ## Brief change log
   
   - Introducing a new Flink busy join reorder strategy named `FlinkBusyJoinReorderRule`
   - Adding plan tests
   - Adding ITCase
   
   
   ## Verifying this change
   
   - Adding plan tests:  `FlinkBusyJoinReorderRuleTest`
   - Adding ITCase: `JoinReorderITCase` in stream and batch mode
   
   ## Does this pull request potentially affect one of the following parts:
   
     - Dependencies (does it add or upgrade a dependency): no
     - The public API, i.e., is any changed class annotated with `@Public(Evolving)`: no
     - The serializers: no
     - The runtime per-record code paths (performance sensitive): no
     - Anything that affects deployment or recovery: JobManager (and its components), Checkpointing, Kubernetes/Yarn, ZooKeeper:no
     - The S3 file system connector: no
   ## Documentation
   
     - Does this pull request introduce a new feature? yes
     - If yes, how is the feature documented? no docs
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] godfreyhe commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
godfreyhe commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1064356565


##########
docs/layouts/shortcodes/generated/optimizer_config_configuration.html:
##########
@@ -41,6 +41,12 @@
             <td>Boolean</td>
             <td>Enables join reorder in optimizer. Default is disabled.</td>
         </tr>
+        <tr>
+            <td><h5>table.optimizer.bushy-join-reorder-threshold</h5><br> <span class="label label-primary">Batch</span> <span class="label label-primary">Streaming</span></td>
+            <td style="word-wrap: break-word;">12</td>
+            <td>Integer</td>
+            <td>The maximum number of joined nodes allowed in the bushy join reorder algorithm. If this parameter is set too large, the search space of join reorder algorithm will be too large, which will spend so much time. the default value is 12.</td>

Review Comment:
   The maximum number of joined nodes allowed in the bushy join reorder algorithm, otherwise the left-deep join reorder algorithm will be used. The search space of bushy join reorder algorithm will increase with the increase of threshold value, so this threshold is not recommended to be too large. The default value is 12.



##########
flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/rules/logical/RewriteMultiJoinConditionRuleTest.scala:
##########
@@ -75,6 +76,9 @@ class RewriteMultiJoinConditionRuleTest extends TableTestBase {
     util.addTableSource[(Int, Long)]("B", 'b1, 'b2)
     util.addTableSource[(Int, Long)]("C", 'c1, 'c2)
     util.addTableSource[(Int, Long)]("D", 'd1, 'd2)
+
+    util.getTableEnv.getConfig

Review Comment:
   it's better this test can verify bushy join order and left-deep join order



##########
flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/logical/RewriteMultiJoinConditionRule.scala:
##########
@@ -45,7 +48,12 @@ class RewriteMultiJoinConditionRule
     // currently only supports all join types are INNER join
     val isAllInnerJoin = multiJoin.getJoinTypes.forall(_ eq JoinRelType.INNER)
     val (equiJoinFilters, _) = partitionJoinFilters(multiJoin)
-    !multiJoin.isFullOuterJoin && isAllInnerJoin && equiJoinFilters.size > 1
+    val numJoinInputs = multiJoin.getInputs.size()
+    val dpThreshold = ShortcutUtils
+      .unwrapContext(multiJoin)
+      .getTableConfig
+      .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD)
+    (numJoinInputs > dpThreshold) && !multiJoin.isFullOuterJoin && isAllInnerJoin && equiJoinFilters.size > 1

Review Comment:
   why we need to change this?



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkJoinReorderRule.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.planner.utils.ShortcutUtils;
+
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+
+/**
+ * Flink join reorder rule, which can change the join order of input relNode tree.
+ *
+ * <p>It is triggered by the ({@link MultiJoin}).
+ *
+ * <p>In this rule, there are two specific join reorder strategies can be chosen, one is {@link
+ * LoptOptimizeJoinRule}, another is {@link FlinkBushyJoinReorderRule}. Which rule is selected
+ * depends on the parameter {@link
+ * OptimizerConfigOptions#TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD}.
+ */
+public class FlinkJoinReorderRule extends RelRule<FlinkJoinReorderRule.Config>
+        implements TransformationRule {
+
+    public static final FlinkJoinReorderRule INSTANCE =
+            FlinkJoinReorderRule.Config.DEFAULT.toRule();
+
+    private static final LoptOptimizeJoinRule LOPT_JOIN_REORDER =
+            LoptOptimizeJoinRule.Config.DEFAULT.toRule();
+
+    private static final FlinkBushyJoinReorderRule BUSHY_JOIN_REORDER =
+            FlinkBushyJoinReorderRule.Config.DEFAULT.toRule();
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkJoinReorderRule(FlinkJoinReorderRule.Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(
+                FlinkJoinReorderRule.Config.DEFAULT
+                        .withRelBuilderFactory(relBuilderFactory)
+                        .as(FlinkJoinReorderRule.Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final MultiJoin multiJoinRel = call.rel(0);
+        int numJoinInputs = multiJoinRel.getInputs().size();
+        int dpThreshold =

Review Comment:
   nit: rename to bushyTreeThreshold ?



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                joinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(
+            RelMetadataQuery mq, Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan, mq)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    relBuilder, oneSidePlan, otherSidePlan, multiJoin, isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlan == null
+                                || newJoinPlan.get().betterThan(existingPlan, mq)) {
+                            nextLevel.put(newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.factorIds);
+        resSet.retainAll(otherSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.factorIds, otherSidePlan.factorIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.factorIds.size() >= otherSidePlan.factorIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newFactorIds.addAll(leftPlan.factorIds);
+        newFactorIds.addAll(rightPlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.factorIds),
+                        new ArrayList<>(rightPlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(leftPlan.relNode)
+                                .push(rightPlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        return Optional.of(new JoinPlan(newFactorIds, newJoin));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftFactorIds,
+            List<Integer> rightFactorIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBushyJoin(
+                                        rexBuilder, multiJoin, leftFactorIds, rightFactorIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneFactorIds,
+            Set<Integer> otherFactorIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneFactorIds.size() + otherFactorIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneFactorNumbers = 0;
+                int otherFactorNumbers = 0;
+                for (int oneFactorId : oneFactorIds) {
+                    if (factorsRefByJoinFilter.get(oneFactorId)) {
+                        oneFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneFactorId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherFactorId : otherFactorIds) {
+                    if (factorsRefByJoinFilter.get(otherFactorId)) {
+                        otherFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherFactorId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneFactorNumbers > 0
+                        && otherFactorNumbers > 0
+                        && oneFactorNumbers + otherFactorNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> factorIds;
+        final RelNode relNode;
+
+        JoinPlan(LinkedHashSet<Integer> factorIds, RelNode relNode) {
+            this.factorIds = factorIds;
+            this.relNode = relNode;
+        }
+
+        private boolean betterThan(JoinPlan otherPlan, RelMetadataQuery mq) {
+            RelOptCost thisCost = mq.getCumulativeCost(this.relNode);

Review Comment:
   we can get RelMetadataQuery from RelNode



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                joinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(
+            RelMetadataQuery mq, Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan, mq)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    relBuilder, oneSidePlan, otherSidePlan, multiJoin, isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlan == null
+                                || newJoinPlan.get().betterThan(existingPlan, mq)) {
+                            nextLevel.put(newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.factorIds);
+        resSet.retainAll(otherSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.factorIds, otherSidePlan.factorIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.factorIds.size() >= otherSidePlan.factorIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newFactorIds.addAll(leftPlan.factorIds);
+        newFactorIds.addAll(rightPlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.factorIds),
+                        new ArrayList<>(rightPlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(leftPlan.relNode)
+                                .push(rightPlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        return Optional.of(new JoinPlan(newFactorIds, newJoin));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftFactorIds,
+            List<Integer> rightFactorIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBushyJoin(
+                                        rexBuilder, multiJoin, leftFactorIds, rightFactorIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneFactorIds,
+            Set<Integer> otherFactorIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneFactorIds.size() + otherFactorIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneFactorNumbers = 0;
+                int otherFactorNumbers = 0;
+                for (int oneFactorId : oneFactorIds) {
+                    if (factorsRefByJoinFilter.get(oneFactorId)) {
+                        oneFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneFactorId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherFactorId : otherFactorIds) {
+                    if (factorsRefByJoinFilter.get(otherFactorId)) {
+                        otherFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherFactorId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneFactorNumbers > 0
+                        && otherFactorNumbers > 0
+                        && oneFactorNumbers + otherFactorNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> factorIds;
+        final RelNode relNode;
+
+        JoinPlan(LinkedHashSet<Integer> factorIds, RelNode relNode) {
+            this.factorIds = factorIds;
+            this.relNode = relNode;
+        }
+
+        private boolean betterThan(JoinPlan otherPlan, RelMetadataQuery mq) {
+            RelOptCost thisCost = mq.getCumulativeCost(this.relNode);
+            RelOptCost otherCost = mq.getCumulativeCost(otherPlan.relNode);
+            if (thisCost == null
+                    || otherCost == null
+                    || thisCost.getRows() == 0.0
+                    || otherCost.getRows() == 0.0) {
+                return false;
+            } else {
+                return thisCost.isLt(otherCost);
+            }
+        }
+    }
+
+    private static class RexInputConverterForBushyJoin extends RexShuttle {
+        private final RexBuilder rexBuilder;
+        private final LoptMultiJoin multiJoin;
+        private final List<Integer> leftFactorIds;
+        private final List<Integer> rightFactorIds;
+
+        public RexInputConverterForBushyJoin(
+                RexBuilder rexBuilder,
+                LoptMultiJoin multiJoin,
+                List<Integer> leftFactorIds,
+                List<Integer> rightFactorIds) {
+            this.rexBuilder = rexBuilder;

Review Comment:
   we can get RexBuilder from RelNode



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>

Review Comment:
   add some comment at the core path to help reading



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/batch/sql/join/JoinReorderITCase.java:
##########
@@ -0,0 +1,276 @@
+/*
+ * 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.runtime.batch.sql.join;
+
+import org.apache.flink.table.api.TableEnvironment;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
+import org.apache.flink.table.planner.factories.TestValuesTableFactory;
+import org.apache.flink.table.planner.runtime.utils.BatchTestBase;
+import org.apache.flink.table.planner.runtime.utils.TestData;
+import org.apache.flink.table.planner.utils.JavaScalaConversionUtil;
+import org.apache.flink.types.Row;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import scala.Enumeration;
+
+/** ITCase for JoinReorder in batch mode. */
+@RunWith(Parameterized.class)
+public class JoinReorderITCase extends BatchTestBase {
+
+    private TableEnvironment tEnv;
+    private Catalog catalog;
+
+    @Parameterized.Parameter(value = 0)
+    public Enumeration.Value expectedJoinType;
+
+    @Parameterized.Parameter(value = 1)
+    public boolean isBushyJoinReorder;
+
+    @Parameterized.Parameters(name = "expectedJoinType={0}, isBushyJoinReorder={1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[][] {
+                    {JoinType.BroadcastHashJoin(), false},
+                    {JoinType.BroadcastHashJoin(), true},
+                    {JoinType.HashJoin(), false},
+                    {JoinType.HashJoin(), true},
+                    {JoinType.SortMergeJoin(), false},
+                    {JoinType.SortMergeJoin(), true},
+                    {JoinType.NestedLoopJoin(), false},
+                    {JoinType.NestedLoopJoin(), true}
+                });
+    }
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+        tEnv = tEnv();
+        catalog = tEnv.getCatalog(tEnv.getCurrentCatalog()).get();
+
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(ExecutionConfigOptions.TABLE_EXEC_RESOURCE_DEFAULT_PARALLELISM, 1);

Review Comment:
   why we need set the parallelism to 1?



##########
flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRuleTest.xml:
##########
@@ -0,0 +1,198 @@
+<?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>

Review Comment:
   it seems that all optimized plan are not bushy tree



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }

Review Comment:
   can you explain this part, it's a little confuse



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.

Review Comment:
   it's better we can add more comments about left/right/full case



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                joinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(
+            RelMetadataQuery mq, Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan, mq)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    relBuilder, oneSidePlan, otherSidePlan, multiJoin, isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlan == null
+                                || newJoinPlan.get().betterThan(existingPlan, mq)) {
+                            nextLevel.put(newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {

Review Comment:
   only inner joins can be here



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                joinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(
+            RelMetadataQuery mq, Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan, mq)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    relBuilder, oneSidePlan, otherSidePlan, multiJoin, isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlan == null
+                                || newJoinPlan.get().betterThan(existingPlan, mq)) {
+                            nextLevel.put(newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.factorIds);
+        resSet.retainAll(otherSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.factorIds, otherSidePlan.factorIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.factorIds.size() >= otherSidePlan.factorIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newFactorIds.addAll(leftPlan.factorIds);
+        newFactorIds.addAll(rightPlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.factorIds),
+                        new ArrayList<>(rightPlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(leftPlan.relNode)
+                                .push(rightPlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        return Optional.of(new JoinPlan(newFactorIds, newJoin));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftFactorIds,
+            List<Integer> rightFactorIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBushyJoin(
+                                        rexBuilder, multiJoin, leftFactorIds, rightFactorIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneFactorIds,
+            Set<Integer> otherFactorIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneFactorIds.size() + otherFactorIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneFactorNumbers = 0;
+                int otherFactorNumbers = 0;
+                for (int oneFactorId : oneFactorIds) {
+                    if (factorsRefByJoinFilter.get(oneFactorId)) {
+                        oneFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneFactorId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherFactorId : otherFactorIds) {
+                    if (factorsRefByJoinFilter.get(otherFactorId)) {
+                        otherFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherFactorId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneFactorNumbers > 0
+                        && otherFactorNumbers > 0
+                        && oneFactorNumbers + otherFactorNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> factorIds;
+        final RelNode relNode;
+
+        JoinPlan(LinkedHashSet<Integer> factorIds, RelNode relNode) {
+            this.factorIds = factorIds;
+            this.relNode = relNode;
+        }
+
+        private boolean betterThan(JoinPlan otherPlan, RelMetadataQuery mq) {
+            RelOptCost thisCost = mq.getCumulativeCost(this.relNode);
+            RelOptCost otherCost = mq.getCumulativeCost(otherPlan.relNode);
+            if (thisCost == null
+                    || otherCost == null
+                    || thisCost.getRows() == 0.0

Review Comment:
   why we need to check `thisCost.getRows() == 0.0` ?



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(

Review Comment:
   nit: reorderInnerJoin



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                joinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(
+            RelMetadataQuery mq, Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan, mq)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    relBuilder, oneSidePlan, otherSidePlan, multiJoin, isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlan == null
+                                || newJoinPlan.get().betterThan(existingPlan, mq)) {
+                            nextLevel.put(newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.factorIds);
+        resSet.retainAll(otherSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.factorIds, otherSidePlan.factorIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.factorIds.size() >= otherSidePlan.factorIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newFactorIds.addAll(leftPlan.factorIds);
+        newFactorIds.addAll(rightPlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.factorIds),
+                        new ArrayList<>(rightPlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =

Review Comment:
   it's more safe the relBuilder is cleared before building tree



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065314128


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                joinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(
+            RelMetadataQuery mq, Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan, mq)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    relBuilder, oneSidePlan, otherSidePlan, multiJoin, isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlan == null
+                                || newJoinPlan.get().betterThan(existingPlan, mq)) {
+                            nextLevel.put(newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {

Review Comment:
   > only inner joins can be here
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on PR #21530:
URL: https://github.com/apache/flink/pull/21530#issuecomment-1381472821

   @flinkbot run azure


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on PR #21530:
URL: https://github.com/apache/flink/pull/21530#issuecomment-1383325963

   @flinkbot run azure


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065269586


##########
flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/logical/RewriteMultiJoinConditionRule.scala:
##########
@@ -45,7 +48,12 @@ class RewriteMultiJoinConditionRule
     // currently only supports all join types are INNER join
     val isAllInnerJoin = multiJoin.getJoinTypes.forall(_ eq JoinRelType.INNER)
     val (equiJoinFilters, _) = partitionJoinFilters(multiJoin)
-    !multiJoin.isFullOuterJoin && isAllInnerJoin && equiJoinFilters.size > 1
+    val numJoinInputs = multiJoin.getInputs.size()
+    val dpThreshold = ShortcutUtils
+      .unwrapContext(multiJoin)
+      .getTableConfig
+      .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD)
+    (numJoinInputs > dpThreshold) && !multiJoin.isFullOuterJoin && isAllInnerJoin && equiJoinFilters.size > 1

Review Comment:
   For bushy join reorder, this is no need to rewrite multi join condition to avoid increasing the search base of the bushy join reorder algorithm. Like `A J B J C`, `A and B` have join condition,  `B and C` have join condition. `RewriteMultiJoinConditionRule` will add a new condition `A and C`: this is useful for Lopt join rule, but for bushy join rule,  the condition `A and B` and  `B and C` can get two three-tables join reorder result( `(A J B) J C` and `(B J C) J A`, and only one `A J B J C` result will be retained). Btw, I will add comment for why will do it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066494317


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(

Review Comment:
   > addOuterJoinToTop
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066492728


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    /**
+     * Creates the topmost projection that will sit on top of the selected join ordering. The
+     * projection needs to match the original join ordering. Also, places any post-join filters on
+     * top of the project.
+     */
+    private static RelNode creatTopProject(

Review Comment:
   > createTopProject
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] flinkbot commented on pull request #21530: [FLINK-30376][table-planner] Introduce a new flink busy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
flinkbot commented on PR #21530:
URL: https://github.com/apache/flink/pull/21530#issuecomment-1357765946

   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "c139c227d20dd5e3406ac080d3efcc0e1c106e9d",
       "status" : "UNKNOWN",
       "url" : "TBD",
       "triggerID" : "c139c227d20dd5e3406ac080d3efcc0e1c106e9d",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * c139c227d20dd5e3406ac080d3efcc0e1c106e9d UNKNOWN
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run azure` re-run the last Azure build
   </details>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065269586


##########
flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/logical/RewriteMultiJoinConditionRule.scala:
##########
@@ -45,7 +48,12 @@ class RewriteMultiJoinConditionRule
     // currently only supports all join types are INNER join
     val isAllInnerJoin = multiJoin.getJoinTypes.forall(_ eq JoinRelType.INNER)
     val (equiJoinFilters, _) = partitionJoinFilters(multiJoin)
-    !multiJoin.isFullOuterJoin && isAllInnerJoin && equiJoinFilters.size > 1
+    val numJoinInputs = multiJoin.getInputs.size()
+    val dpThreshold = ShortcutUtils
+      .unwrapContext(multiJoin)
+      .getTableConfig
+      .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD)
+    (numJoinInputs > dpThreshold) && !multiJoin.isFullOuterJoin && isAllInnerJoin && equiJoinFilters.size > 1

Review Comment:
   For bushy join reorder, this is no need to rewrite multi join condition to avoid increasing the search base of the bushy join reorder algorithm, but it will not bring more possible join pairs for bushy join. Like `A J B J C`, `A and B` have join condition,  `B and C` have join condition. `RewriteMultiJoinConditionRule` will add a new condition `A and C`: this is useful for Lopt join rule, but for bushy join rule,  the condition `A and B` and  `B and C` can get two three-tables join reorder result( `(A J B) J C` and `(B J C) J A`, and only one `A J B J C` result will be retained). Btw, I will add comment for why will do it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065350761


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }

Review Comment:
   > can you explain this part, it's a little confuse
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065381018


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.

Review Comment:
   > it's better we can add more comments about left/right/full case
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on PR #21530:
URL: https://github.com/apache/flink/pull/21530#issuecomment-1381541979

   @flinkbot run azure


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065313823


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                joinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(
+            RelMetadataQuery mq, Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan, mq)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    relBuilder, oneSidePlan, otherSidePlan, multiJoin, isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlan == null
+                                || newJoinPlan.get().betterThan(existingPlan, mq)) {
+                            nextLevel.put(newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.factorIds);
+        resSet.retainAll(otherSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.factorIds, otherSidePlan.factorIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.factorIds.size() >= otherSidePlan.factorIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newFactorIds.addAll(leftPlan.factorIds);
+        newFactorIds.addAll(rightPlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.factorIds),
+                        new ArrayList<>(rightPlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =

Review Comment:
   > it's more safe the relBuilder is cleared before building tree
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on PR #21530:
URL: https://github.com/apache/flink/pull/21530#issuecomment-1383621697

   @flinkbot run azure


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065259646


##########
docs/layouts/shortcodes/generated/optimizer_config_configuration.html:
##########
@@ -89,5 +89,17 @@
             <td>Boolean</td>
             <td>When it is true, the optimizer will collect and use the statistics from source connectors if the source extends from SupportsStatisticReport and the statistics from catalog is UNKNOWN.Default value is true.</td>
         </tr>
+        <tr>
+            <td><h5>table.optimizer.busy-join-reorder</h5><br> <span class="label label-primary">Batch</span> <span class="label label-primary">Streaming</span></td>
+            <td style="word-wrap: break-word;">false</td>
+            <td>Boolean</td>
+            <td>Enables busy join reorder in optimizer. Default is disabled.</td>
+        </tr>
+        <tr>
+            <td><h5>table.optimizer.busy-join-reorder-dp-threshold</h5><br> <span class="label label-primary">Batch</span> <span class="label label-primary">Streaming</span></td>

Review Comment:
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065265984


##########
docs/layouts/shortcodes/generated/optimizer_config_configuration.html:
##########
@@ -41,6 +41,12 @@
             <td>Boolean</td>
             <td>Enables join reorder in optimizer. Default is disabled.</td>
         </tr>
+        <tr>
+            <td><h5>table.optimizer.bushy-join-reorder-threshold</h5><br> <span class="label label-primary">Batch</span> <span class="label label-primary">Streaming</span></td>
+            <td style="word-wrap: break-word;">12</td>
+            <td>Integer</td>
+            <td>The maximum number of joined nodes allowed in the bushy join reorder algorithm. If this parameter is set too large, the search space of join reorder algorithm will be too large, which will spend so much time. the default value is 12.</td>

Review Comment:
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] godfreyhe commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink busy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
godfreyhe commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1057178039


##########
docs/layouts/shortcodes/generated/optimizer_config_configuration.html:
##########
@@ -89,5 +89,17 @@
             <td>Boolean</td>
             <td>When it is true, the optimizer will collect and use the statistics from source connectors if the source extends from SupportsStatisticReport and the statistics from catalog is UNKNOWN.Default value is true.</td>
         </tr>
+        <tr>
+            <td><h5>table.optimizer.busy-join-reorder</h5><br> <span class="label label-primary">Batch</span> <span class="label label-primary">Streaming</span></td>
+            <td style="word-wrap: break-word;">false</td>
+            <td>Boolean</td>
+            <td>Enables busy join reorder in optimizer. Default is disabled.</td>
+        </tr>
+        <tr>
+            <td><h5>table.optimizer.busy-join-reorder-dp-threshold</h5><br> <span class="label label-primary">Batch</span> <span class="label label-primary">Streaming</span></td>

Review Comment:
   for introducing public option, we should post a FLIP or a DISCUSS.
   
   we can remove keyword `dp` from the option since both join reorder programs using dynamic programming algorithm.  just like: `table.optimizer.busy-join-reorder-threshold`



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBusyJoinReorderRule.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+import org.apache.flink.table.planner.plan.utils.FlinkRelOptUtil;
+import org.apache.flink.table.planner.utils.ShortcutUtils;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink busy join reorder rule, which will convert {@link MultiJoin} to a busy join tree.
+ *
+ * <p>In this busy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBusyJoinReorderRule extends RelRule<FlinkBusyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    public static final LoptOptimizeJoinRule MULTI_JOIN_OPTIMIZE =
+            LoptOptimizeJoinRule.Config.DEFAULT.toRule();
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBusyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {

Review Comment:
   If's better we can introduce a `FlinkJoinReorderRule`, which could choose the specific join reorder algorithm based on  `dpThreshold `



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBusyJoinReorderRule.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+import org.apache.flink.table.planner.plan.utils.FlinkRelOptUtil;
+import org.apache.flink.table.planner.utils.ShortcutUtils;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink busy join reorder rule, which will convert {@link MultiJoin} to a busy join tree.
+ *
+ * <p>In this busy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBusyJoinReorderRule extends RelRule<FlinkBusyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    public static final LoptOptimizeJoinRule MULTI_JOIN_OPTIMIZE =
+            LoptOptimizeJoinRule.Config.DEFAULT.toRule();
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBusyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        Boolean enableBusyJoin =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER);
+        Integer dpThreshold =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER_DP_THRESHOLD);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+
+        if (enableBusyJoin && numJoinFactors <= dpThreshold) {
+            RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+            call.transformTo(bestOrder);
+        } else {
+            MULTI_JOIN_OPTIMIZE.onMatch(call);
+        }
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our busy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                RelOptCost cost = mq.getCumulativeCost(joinFactor);
+                joinPlanMap.put(
+                        set1,
+                        new JoinPlan(
+                                set2,
+                                joinFactor,
+                                new FlinkCost(
+                                        cost.getRows(), cost.getCpu(), cost.getIo(), 0.0, 0.0)));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().cost.isLt(bestPlan.cost)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.itemIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.itemIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, List<JoinPlan>> printNextLevel = new LinkedHashMap<>();
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    mq,
+                                    relBuilder,
+                                    oneSidePlan,
+                                    otherSidePlan,
+                                    multiJoin,
+                                    isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().itemIds);
+                        // check if it's the first plan for the item set, or it's a better plan than
+                        // the existing one due to lower cost.
+                        if (existingPlan == null || newJoinPlan.get().betterThan(existingPlan)) {
+                            nextLevel.put(newJoinPlan.get().itemIds, newJoinPlan.get());
+                        }
+
+                        if (printNextLevel.get(newJoinPlan.get().itemIds) == null) {
+                            printNextLevel.put(
+                                    newJoinPlan.get().itemIds,
+                                    Collections.singletonList(newJoinPlan.get()));
+                        } else {
+                            List<JoinPlan> joinPlans =
+                                    new ArrayList<>(printNextLevel.get(newJoinPlan.get().itemIds));
+                            joinPlans.add(newJoinPlan.get());
+                            printNextLevel.put(newJoinPlan.get().itemIds, joinPlans);
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+
+        // print
+        for (Map.Entry<Set<Integer>, List<JoinPlan>> entry : printNextLevel.entrySet()) {
+            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++");
+            System.out.printf("item sets: %s%n", entry.getKey());
+            for (JoinPlan joinPlan : entry.getValue()) {
+                System.out.println("--------------------------------------------");
+                System.out.printf("costs: %s%n", joinPlan.cost.getRows());
+                System.out.println(
+                        FlinkRelOptUtil.toString(
+                                joinPlan.relNode,
+                                SqlExplainLevel.ALL_ATTRIBUTES,
+                                false,
+                                false,
+                                false,
+                                false,
+                                false));
+                System.out.println("--------------------------------------------");
+            }
+            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++");
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping item sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.itemIds);
+        resSet.retainAll(otherSidePlan.itemIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.itemIds, otherSidePlan.itemIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newItemIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.itemIds.size() >= otherSidePlan.itemIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newItemIds.addAll(leftPlan.itemIds);
+        newItemIds.addAll(rightPlan.itemIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.itemIds),
+                        new ArrayList<>(rightPlan.itemIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(leftPlan.relNode)
+                                .push(rightPlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        RelOptCost cost = mq.getCumulativeCost(newJoin);
+        return Optional.of(
+                new JoinPlan(
+                        newItemIds,
+                        newJoin,
+                        new FlinkCost(cost.getRows(), cost.getCpu(), cost.getIo(), 0.0, 0.0)));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftItemIds,
+            List<Integer> rightItemIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBusyJoin(
+                                        rexBuilder, multiJoin, leftItemIds, rightItemIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneItemIds,
+            Set<Integer> otherItemIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneItemIds.size() + otherItemIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneItemNumbers = 0;
+                int otherItemNumbers = 0;
+                for (int oneItemId : oneItemIds) {
+                    if (factorsRefByJoinFilter.get(oneItemId)) {
+                        oneItemNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneItemId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherItemId : otherItemIds) {
+                    if (factorsRefByJoinFilter.get(otherItemId)) {
+                        otherItemNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherItemId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneItemNumbers > 0
+                        && otherItemNumbers > 0
+                        && oneItemNumbers + otherItemNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> itemIds;
+        final RelNode relNode;
+        final FlinkCost cost;

Review Comment:
   the cost can be recompute via `mq.getCumulativeCost`, this cost is used just for caching to speed the computation up ?



##########
flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/config/OptimizerConfigOptions.java:
##########
@@ -135,6 +135,24 @@ public class OptimizerConfigOptions {
                     .defaultValue(false)
                     .withDescription("Enables join reorder in optimizer. Default is disabled.");
 
+    @Documentation.TableOption(execMode = Documentation.ExecMode.BATCH_STREAMING)
+    public static final ConfigOption<Boolean> TABLE_OPTIMIZER_BUSY_JOIN_REORDER =
+            key("table.optimizer.busy-join-reorder")
+                    .booleanType()
+                    .defaultValue(false)
+                    .withDescription(
+                            "Enables busy join reorder in optimizer. Default is disabled.");
+
+    @Documentation.TableOption(execMode = Documentation.ExecMode.BATCH_STREAMING)
+    public static final ConfigOption<Integer> TABLE_OPTIMIZER_BUSY_JOIN_REORDER_DP_THRESHOLD =
+            key("table.optimizer.busy-join-reorder-dp-threshold")
+                    .intType()
+                    .defaultValue(12)

Review Comment:
   can you explain why we choose `12` as the threshold value ?



##########
docs/layouts/shortcodes/generated/optimizer_config_configuration.html:
##########
@@ -89,5 +89,17 @@
             <td>Boolean</td>
             <td>When it is true, the optimizer will collect and use the statistics from source connectors if the source extends from SupportsStatisticReport and the statistics from catalog is UNKNOWN.Default value is true.</td>
         </tr>
+        <tr>
+            <td><h5>table.optimizer.busy-join-reorder</h5><br> <span class="label label-primary">Batch</span> <span class="label label-primary">Streaming</span></td>

Review Comment:
   This option can be removed, since we can use `table.optimizer.busy-join-reorder-dp-threshold` <= 0 to disable busy-join-reorder



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBusyJoinReorderRule.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+import org.apache.flink.table.planner.plan.utils.FlinkRelOptUtil;
+import org.apache.flink.table.planner.utils.ShortcutUtils;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink busy join reorder rule, which will convert {@link MultiJoin} to a busy join tree.
+ *
+ * <p>In this busy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBusyJoinReorderRule extends RelRule<FlinkBusyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    public static final LoptOptimizeJoinRule MULTI_JOIN_OPTIMIZE =
+            LoptOptimizeJoinRule.Config.DEFAULT.toRule();
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBusyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        Boolean enableBusyJoin =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER);
+        Integer dpThreshold =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER_DP_THRESHOLD);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+
+        if (enableBusyJoin && numJoinFactors <= dpThreshold) {
+            RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+            call.transformTo(bestOrder);
+        } else {
+            MULTI_JOIN_OPTIMIZE.onMatch(call);
+        }
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our busy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                RelOptCost cost = mq.getCumulativeCost(joinFactor);
+                joinPlanMap.put(
+                        set1,
+                        new JoinPlan(
+                                set2,
+                                joinFactor,
+                                new FlinkCost(
+                                        cost.getRows(), cost.getCpu(), cost.getIo(), 0.0, 0.0)));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().cost.isLt(bestPlan.cost)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.itemIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.itemIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, List<JoinPlan>> printNextLevel = new LinkedHashMap<>();
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    mq,
+                                    relBuilder,
+                                    oneSidePlan,
+                                    otherSidePlan,
+                                    multiJoin,
+                                    isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().itemIds);
+                        // check if it's the first plan for the item set, or it's a better plan than
+                        // the existing one due to lower cost.
+                        if (existingPlan == null || newJoinPlan.get().betterThan(existingPlan)) {
+                            nextLevel.put(newJoinPlan.get().itemIds, newJoinPlan.get());
+                        }
+
+                        if (printNextLevel.get(newJoinPlan.get().itemIds) == null) {
+                            printNextLevel.put(
+                                    newJoinPlan.get().itemIds,
+                                    Collections.singletonList(newJoinPlan.get()));
+                        } else {
+                            List<JoinPlan> joinPlans =
+                                    new ArrayList<>(printNextLevel.get(newJoinPlan.get().itemIds));
+                            joinPlans.add(newJoinPlan.get());
+                            printNextLevel.put(newJoinPlan.get().itemIds, joinPlans);
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+
+        // print
+        for (Map.Entry<Set<Integer>, List<JoinPlan>> entry : printNextLevel.entrySet()) {
+            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++");

Review Comment:
   the `print` code can be removed



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBusyJoinReorderRule.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+import org.apache.flink.table.planner.plan.utils.FlinkRelOptUtil;
+import org.apache.flink.table.planner.utils.ShortcutUtils;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink busy join reorder rule, which will convert {@link MultiJoin} to a busy join tree.
+ *
+ * <p>In this busy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBusyJoinReorderRule extends RelRule<FlinkBusyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    public static final LoptOptimizeJoinRule MULTI_JOIN_OPTIMIZE =
+            LoptOptimizeJoinRule.Config.DEFAULT.toRule();
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBusyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        Boolean enableBusyJoin =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER);
+        Integer dpThreshold =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER_DP_THRESHOLD);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+
+        if (enableBusyJoin && numJoinFactors <= dpThreshold) {
+            RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+            call.transformTo(bestOrder);
+        } else {
+            MULTI_JOIN_OPTIMIZE.onMatch(call);
+        }
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our busy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                RelOptCost cost = mq.getCumulativeCost(joinFactor);
+                joinPlanMap.put(
+                        set1,
+                        new JoinPlan(
+                                set2,
+                                joinFactor,
+                                new FlinkCost(
+                                        cost.getRows(), cost.getCpu(), cost.getIo(), 0.0, 0.0)));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().cost.isLt(bestPlan.cost)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.itemIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.itemIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, List<JoinPlan>> printNextLevel = new LinkedHashMap<>();
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    mq,
+                                    relBuilder,
+                                    oneSidePlan,
+                                    otherSidePlan,
+                                    multiJoin,
+                                    isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().itemIds);
+                        // check if it's the first plan for the item set, or it's a better plan than
+                        // the existing one due to lower cost.
+                        if (existingPlan == null || newJoinPlan.get().betterThan(existingPlan)) {
+                            nextLevel.put(newJoinPlan.get().itemIds, newJoinPlan.get());
+                        }
+
+                        if (printNextLevel.get(newJoinPlan.get().itemIds) == null) {
+                            printNextLevel.put(
+                                    newJoinPlan.get().itemIds,
+                                    Collections.singletonList(newJoinPlan.get()));
+                        } else {
+                            List<JoinPlan> joinPlans =
+                                    new ArrayList<>(printNextLevel.get(newJoinPlan.get().itemIds));
+                            joinPlans.add(newJoinPlan.get());
+                            printNextLevel.put(newJoinPlan.get().itemIds, joinPlans);
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+
+        // print
+        for (Map.Entry<Set<Integer>, List<JoinPlan>> entry : printNextLevel.entrySet()) {
+            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++");
+            System.out.printf("item sets: %s%n", entry.getKey());
+            for (JoinPlan joinPlan : entry.getValue()) {
+                System.out.println("--------------------------------------------");
+                System.out.printf("costs: %s%n", joinPlan.cost.getRows());
+                System.out.println(
+                        FlinkRelOptUtil.toString(
+                                joinPlan.relNode,
+                                SqlExplainLevel.ALL_ATTRIBUTES,
+                                false,
+                                false,
+                                false,
+                                false,
+                                false));
+                System.out.println("--------------------------------------------");
+            }
+            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++");
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping item sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.itemIds);
+        resSet.retainAll(otherSidePlan.itemIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.itemIds, otherSidePlan.itemIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newItemIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.itemIds.size() >= otherSidePlan.itemIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newItemIds.addAll(leftPlan.itemIds);
+        newItemIds.addAll(rightPlan.itemIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.itemIds),
+                        new ArrayList<>(rightPlan.itemIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(leftPlan.relNode)
+                                .push(rightPlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        RelOptCost cost = mq.getCumulativeCost(newJoin);
+        return Optional.of(
+                new JoinPlan(
+                        newItemIds,
+                        newJoin,
+                        new FlinkCost(cost.getRows(), cost.getCpu(), cost.getIo(), 0.0, 0.0)));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftItemIds,
+            List<Integer> rightItemIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBusyJoin(
+                                        rexBuilder, multiJoin, leftItemIds, rightItemIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneItemIds,
+            Set<Integer> otherItemIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneItemIds.size() + otherItemIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneItemNumbers = 0;
+                int otherItemNumbers = 0;
+                for (int oneItemId : oneItemIds) {
+                    if (factorsRefByJoinFilter.get(oneItemId)) {
+                        oneItemNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneItemId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherItemId : otherItemIds) {
+                    if (factorsRefByJoinFilter.get(otherItemId)) {
+                        otherItemNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherItemId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneItemNumbers > 0
+                        && otherItemNumbers > 0
+                        && oneItemNumbers + otherItemNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> itemIds;

Review Comment:
   What does `itemId` mean ?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065279285


##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/batch/sql/join/JoinReorderITCase.java:
##########
@@ -0,0 +1,276 @@
+/*
+ * 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.runtime.batch.sql.join;
+
+import org.apache.flink.table.api.TableEnvironment;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
+import org.apache.flink.table.planner.factories.TestValuesTableFactory;
+import org.apache.flink.table.planner.runtime.utils.BatchTestBase;
+import org.apache.flink.table.planner.runtime.utils.TestData;
+import org.apache.flink.table.planner.utils.JavaScalaConversionUtil;
+import org.apache.flink.types.Row;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import scala.Enumeration;
+
+/** ITCase for JoinReorder in batch mode. */
+@RunWith(Parameterized.class)
+public class JoinReorderITCase extends BatchTestBase {
+
+    private TableEnvironment tEnv;
+    private Catalog catalog;
+
+    @Parameterized.Parameter(value = 0)
+    public Enumeration.Value expectedJoinType;
+
+    @Parameterized.Parameter(value = 1)
+    public boolean isBushyJoinReorder;
+
+    @Parameterized.Parameters(name = "expectedJoinType={0}, isBushyJoinReorder={1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[][] {
+                    {JoinType.BroadcastHashJoin(), false},
+                    {JoinType.BroadcastHashJoin(), true},
+                    {JoinType.HashJoin(), false},
+                    {JoinType.HashJoin(), true},
+                    {JoinType.SortMergeJoin(), false},
+                    {JoinType.SortMergeJoin(), true},
+                    {JoinType.NestedLoopJoin(), false},
+                    {JoinType.NestedLoopJoin(), true}
+                });
+    }
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+        tEnv = tEnv();
+        catalog = tEnv.getCatalog(tEnv.getCurrentCatalog()).get();
+
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(ExecutionConfigOptions.TABLE_EXEC_RESOURCE_DEFAULT_PARALLELISM, 1);

Review Comment:
   > why we need set the parallelism to 1?
   
   Set a value less than 4 (All join inputs number in this test equals 4).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066490161


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ

Review Comment:
   > get ((A IJ C) IJ D). Then, we will add B to the top, get (((A IJ C) IJ D) LJ B).
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066489338


##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/batch/sql/join/JoinReorderITCase.java:
##########
@@ -0,0 +1,276 @@
+/*
+ * 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.runtime.batch.sql.join;
+
+import org.apache.flink.table.api.TableEnvironment;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
+import org.apache.flink.table.planner.factories.TestValuesTableFactory;
+import org.apache.flink.table.planner.runtime.utils.BatchTestBase;
+import org.apache.flink.table.planner.runtime.utils.TestData;
+import org.apache.flink.table.planner.utils.JavaScalaConversionUtil;
+import org.apache.flink.types.Row;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import scala.Enumeration;
+
+/** ITCase for JoinReorder in batch mode. */
+@RunWith(Parameterized.class)
+public class JoinReorderITCase extends BatchTestBase {
+
+    private TableEnvironment tEnv;
+    private Catalog catalog;
+
+    @Parameterized.Parameter(value = 0)
+    public Enumeration.Value expectedJoinType;
+
+    @Parameterized.Parameter(value = 1)
+    public boolean isBushyJoinReorder;
+
+    @Parameterized.Parameters(name = "expectedJoinType={0}, isBushyJoinReorder={1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[][] {
+                    {JoinType.BroadcastHashJoin(), false},
+                    {JoinType.BroadcastHashJoin(), true},
+                    {JoinType.HashJoin(), false},
+                    {JoinType.HashJoin(), true},
+                    {JoinType.SortMergeJoin(), false},
+                    {JoinType.SortMergeJoin(), true},
+                    {JoinType.NestedLoopJoin(), false},
+                    {JoinType.NestedLoopJoin(), true}
+                });
+    }
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+        tEnv = tEnv();
+        catalog = tEnv.getCatalog(tEnv.getCurrentCatalog()).get();
+
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(ExecutionConfigOptions.TABLE_EXEC_RESOURCE_DEFAULT_PARALLELISM, 1);

Review Comment:
   > There is no relationship between them
   
   Sorry, I added it by mistake.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065730432


##########
flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/rules/logical/RewriteMultiJoinConditionRuleTest.scala:
##########
@@ -75,6 +76,9 @@ class RewriteMultiJoinConditionRuleTest extends TableTestBase {
     util.addTableSource[(Int, Long)]("B", 'b1, 'b2)
     util.addTableSource[(Int, Long)]("C", 'c1, 'c2)
     util.addTableSource[(Int, Long)]("D", 'd1, 'd2)
+
+    util.getTableEnv.getConfig

Review Comment:
   > it's better this test can verify bushy join order and left-deep join order
   
   I add some test cases in this class to cover bushy join reorder enable suitation.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on PR #21530:
URL: https://github.com/apache/flink/pull/21530#issuecomment-1381510028

   @flinkbot run azure


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink busy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1058020032


##########
flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/config/OptimizerConfigOptions.java:
##########
@@ -135,6 +135,24 @@ public class OptimizerConfigOptions {
                     .defaultValue(false)
                     .withDescription("Enables join reorder in optimizer. Default is disabled.");
 
+    @Documentation.TableOption(execMode = Documentation.ExecMode.BATCH_STREAMING)
+    public static final ConfigOption<Boolean> TABLE_OPTIMIZER_BUSY_JOIN_REORDER =
+            key("table.optimizer.busy-join-reorder")
+                    .booleanType()
+                    .defaultValue(false)
+                    .withDescription(
+                            "Enables busy join reorder in optimizer. Default is disabled.");
+
+    @Documentation.TableOption(execMode = Documentation.ExecMode.BATCH_STREAMING)
+    public static final ConfigOption<Integer> TABLE_OPTIMIZER_BUSY_JOIN_REORDER_DP_THRESHOLD =
+            key("table.optimizer.busy-join-reorder-dp-threshold")
+                    .intType()
+                    .defaultValue(12)

Review Comment:
   > can you explain why we choose `12` as the threshold value ?
   
   New we set the default value to -1, which means that Flink disable busy join reorder by default. 12 is an empirical parameter provided by the designer of the algorithm.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065315619


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                joinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(
+            RelMetadataQuery mq, Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan, mq)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    relBuilder, oneSidePlan, otherSidePlan, multiJoin, isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlan == null
+                                || newJoinPlan.get().betterThan(existingPlan, mq)) {
+                            nextLevel.put(newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.factorIds);
+        resSet.retainAll(otherSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.factorIds, otherSidePlan.factorIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.factorIds.size() >= otherSidePlan.factorIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newFactorIds.addAll(leftPlan.factorIds);
+        newFactorIds.addAll(rightPlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.factorIds),
+                        new ArrayList<>(rightPlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(leftPlan.relNode)
+                                .push(rightPlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        return Optional.of(new JoinPlan(newFactorIds, newJoin));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftFactorIds,
+            List<Integer> rightFactorIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBushyJoin(
+                                        rexBuilder, multiJoin, leftFactorIds, rightFactorIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneFactorIds,
+            Set<Integer> otherFactorIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneFactorIds.size() + otherFactorIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneFactorNumbers = 0;
+                int otherFactorNumbers = 0;
+                for (int oneFactorId : oneFactorIds) {
+                    if (factorsRefByJoinFilter.get(oneFactorId)) {
+                        oneFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneFactorId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherFactorId : otherFactorIds) {
+                    if (factorsRefByJoinFilter.get(otherFactorId)) {
+                        otherFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherFactorId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneFactorNumbers > 0
+                        && otherFactorNumbers > 0
+                        && oneFactorNumbers + otherFactorNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> factorIds;
+        final RelNode relNode;
+
+        JoinPlan(LinkedHashSet<Integer> factorIds, RelNode relNode) {
+            this.factorIds = factorIds;
+            this.relNode = relNode;
+        }
+
+        private boolean betterThan(JoinPlan otherPlan, RelMetadataQuery mq) {
+            RelOptCost thisCost = mq.getCumulativeCost(this.relNode);

Review Comment:
   > we can get RelMetadataQuery from RelNode
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065316262


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                joinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(
+            RelMetadataQuery mq, Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan, mq)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    relBuilder, oneSidePlan, otherSidePlan, multiJoin, isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlan == null
+                                || newJoinPlan.get().betterThan(existingPlan, mq)) {
+                            nextLevel.put(newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.factorIds);
+        resSet.retainAll(otherSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.factorIds, otherSidePlan.factorIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.factorIds.size() >= otherSidePlan.factorIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newFactorIds.addAll(leftPlan.factorIds);
+        newFactorIds.addAll(rightPlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.factorIds),
+                        new ArrayList<>(rightPlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(leftPlan.relNode)
+                                .push(rightPlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        return Optional.of(new JoinPlan(newFactorIds, newJoin));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftFactorIds,
+            List<Integer> rightFactorIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBushyJoin(
+                                        rexBuilder, multiJoin, leftFactorIds, rightFactorIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneFactorIds,
+            Set<Integer> otherFactorIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneFactorIds.size() + otherFactorIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneFactorNumbers = 0;
+                int otherFactorNumbers = 0;
+                for (int oneFactorId : oneFactorIds) {
+                    if (factorsRefByJoinFilter.get(oneFactorId)) {
+                        oneFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneFactorId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherFactorId : otherFactorIds) {
+                    if (factorsRefByJoinFilter.get(otherFactorId)) {
+                        otherFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherFactorId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneFactorNumbers > 0
+                        && otherFactorNumbers > 0
+                        && oneFactorNumbers + otherFactorNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> factorIds;
+        final RelNode relNode;
+
+        JoinPlan(LinkedHashSet<Integer> factorIds, RelNode relNode) {
+            this.factorIds = factorIds;
+            this.relNode = relNode;
+        }
+
+        private boolean betterThan(JoinPlan otherPlan, RelMetadataQuery mq) {
+            RelOptCost thisCost = mq.getCumulativeCost(this.relNode);
+            RelOptCost otherCost = mq.getCumulativeCost(otherPlan.relNode);
+            if (thisCost == null
+                    || otherCost == null
+                    || thisCost.getRows() == 0.0

Review Comment:
   > why we need to check `thisCost.getRows() == 0.0` ?
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066501816


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(

Review Comment:
   > addCrossJoinToTop
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066499832


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)

Review Comment:
   > Why is it always `JoinRelType.LEFT` here ?
   
   The join type of left join : `((A LJ B) IJ C)` in `MultiJoin` is `[Inner, Left, Inner]`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066492364


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    /**
+     * Creates the topmost projection that will sit on top of the selected join ordering. The
+     * projection needs to match the original join ordering. Also, places any post-join filters on
+     * top of the project.
+     */
+    private static RelNode creatTopProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.clear();
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    /** Found possible join plans for the next level based on the found plans in the prev levels. */
+    private static Map<Set<Integer>, JoinPlan> foundNextLevel(
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> foundPlans,
+            LoptMultiJoin multiJoin) {
+        Map<Set<Integer>, JoinPlan> currentLevelJoinPlanMap = new LinkedHashMap<>();
+        int foundPlansLevel = foundPlans.size() - 1;
+        int joinLeftSideLevel = 0;
+        int joinRightSideLevel = foundPlansLevel;
+        while (joinLeftSideLevel <= joinRightSideLevel) {
+            List<JoinPlan> joinLeftSidePlans =
+                    new ArrayList<>(foundPlans.get(joinLeftSideLevel).values());
+            int planSize = joinLeftSidePlans.size();
+            for (int i = 0; i < planSize; i++) {
+                JoinPlan joinLeftSidePlan = joinLeftSidePlans.get(i);
+                List<JoinPlan> joinRightSidePlans;
+                if (joinLeftSideLevel == joinRightSideLevel) {
+                    // If left side level number equals right side level number. We can remove those
+                    // top 'i' plans which already judged in right side plans to decrease search
+                    // spaces.
+                    joinRightSidePlans = new ArrayList<>(joinLeftSidePlans);
+                    if (i > 0) {
+                        joinRightSidePlans.subList(0, i).clear();
+                    }
+                } else {
+                    joinRightSidePlans =
+                            new ArrayList<>(foundPlans.get(joinRightSideLevel).values());
+                }
+                for (JoinPlan joinRightSidePlan : joinRightSidePlans) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildInnerJoin(
+                                    relBuilder, joinLeftSidePlan, joinRightSidePlan, multiJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlanInCurrentLevel =
+                                currentLevelJoinPlanMap.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlanInCurrentLevel == null
+                                || newJoinPlan.get().betterThan(existingPlanInCurrentLevel)) {
+                            currentLevelJoinPlanMap.put(
+                                    newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            joinLeftSideLevel += 1;
+            joinRightSideLevel = foundPlansLevel - joinLeftSideLevel;

Review Comment:
   > joinLeftSideLevel++; joinRightSideLevel--;
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066506235


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);

Review Comment:
   > can we add some check about the remaining joins are cross join
   > 
   > nit: these code can be extract as a method, which can be used in `addToTopForOuterJoin`
   
   I think there is no need and no way to check it .
   Why no need? : If a factor can be added to `MultiJoin`, it have only three situation: 1) inner join; 2) left/right/full outer join; 3. cross join. So, if the factor is not inner join and outer join, it must be cross join.
   
   Why no way? : `MultiJoin` will not store the join condition of cross join.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] godfreyhe commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
godfreyhe commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065775300


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ

Review Comment:
   get ((A IJ C) IJ D). Then, we will add B to the top, get (((A IJ C) IJ D) LJ B).



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    /**
+     * Creates the topmost projection that will sit on top of the selected join ordering. The
+     * projection needs to match the original join ordering. Also, places any post-join filters on
+     * top of the project.
+     */
+    private static RelNode creatTopProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.clear();
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    /** Found possible join plans for the next level based on the found plans in the prev levels. */
+    private static Map<Set<Integer>, JoinPlan> foundNextLevel(
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> foundPlans,
+            LoptMultiJoin multiJoin) {
+        Map<Set<Integer>, JoinPlan> currentLevelJoinPlanMap = new LinkedHashMap<>();
+        int foundPlansLevel = foundPlans.size() - 1;
+        int joinLeftSideLevel = 0;
+        int joinRightSideLevel = foundPlansLevel;
+        while (joinLeftSideLevel <= joinRightSideLevel) {
+            List<JoinPlan> joinLeftSidePlans =
+                    new ArrayList<>(foundPlans.get(joinLeftSideLevel).values());
+            int planSize = joinLeftSidePlans.size();
+            for (int i = 0; i < planSize; i++) {
+                JoinPlan joinLeftSidePlan = joinLeftSidePlans.get(i);
+                List<JoinPlan> joinRightSidePlans;
+                if (joinLeftSideLevel == joinRightSideLevel) {
+                    // If left side level number equals right side level number. We can remove those
+                    // top 'i' plans which already judged in right side plans to decrease search
+                    // spaces.
+                    joinRightSidePlans = new ArrayList<>(joinLeftSidePlans);
+                    if (i > 0) {
+                        joinRightSidePlans.subList(0, i).clear();
+                    }
+                } else {
+                    joinRightSidePlans =
+                            new ArrayList<>(foundPlans.get(joinRightSideLevel).values());
+                }
+                for (JoinPlan joinRightSidePlan : joinRightSidePlans) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildInnerJoin(
+                                    relBuilder, joinLeftSidePlan, joinRightSidePlan, multiJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlanInCurrentLevel =
+                                currentLevelJoinPlanMap.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlanInCurrentLevel == null
+                                || newJoinPlan.get().betterThan(existingPlanInCurrentLevel)) {
+                            currentLevelJoinPlanMap.put(
+                                    newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            joinLeftSideLevel += 1;
+            joinRightSideLevel = foundPlansLevel - joinLeftSideLevel;
+        }
+        return currentLevelJoinPlanMap;
+    }
+
+    private static Optional<JoinPlan> buildInnerJoin(
+            RelBuilder relBuilder,
+            JoinPlan leftSidePlan,
+            JoinPlan rightSidePlan,
+            LoptMultiJoin multiJoin) {
+        // Intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(leftSidePlan.factorIds);
+        resSet.retainAll(rightSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                getConditionsAndJoinType(
+                        leftSidePlan.factorIds, rightSidePlan.factorIds, multiJoin, false);
+        if (!joinConditions.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConditions.get().f0;
+        JoinRelType joinType = joinConditions.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan newLeftSidePlan;
+        JoinPlan newRightSidePlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (leftSidePlan.factorIds.size() >= rightSidePlan.factorIds.size()) {
+            newLeftSidePlan = leftSidePlan;
+            newRightSidePlan = rightSidePlan;
+        } else {
+            newLeftSidePlan = rightSidePlan;
+            newRightSidePlan = leftSidePlan;
+        }
+        newFactorIds.addAll(newLeftSidePlan.factorIds);
+        newFactorIds.addAll(newRightSidePlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(newLeftSidePlan.factorIds),
+                        new ArrayList<>(newRightSidePlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        relBuilder.clear();
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(newLeftSidePlan.relNode)
+                                .push(newRightSidePlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        return Optional.of(new JoinPlan(newFactorIds, newJoin));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftFactorIds,
+            List<Integer> rightFactorIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBushyJoin(
+                                        rexBuilder, multiJoin, leftFactorIds, rightFactorIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneFactorIds,
+            Set<Integer> otherFactorIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneFactorIds.size() + otherFactorIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneFactorNumbers = 0;
+                int otherFactorNumbers = 0;
+                for (int oneFactorId : oneFactorIds) {
+                    if (factorsRefByJoinFilter.get(oneFactorId)) {
+                        oneFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneFactorId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherFactorId : otherFactorIds) {
+                    if (factorsRefByJoinFilter.get(otherFactorId)) {
+                        otherFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherFactorId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneFactorNumbers > 0
+                        && otherFactorNumbers > 0
+                        && oneFactorNumbers + otherFactorNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> factorIds;
+        final RelNode relNode;
+
+        JoinPlan(LinkedHashSet<Integer> factorIds, RelNode relNode) {
+            this.factorIds = factorIds;
+            this.relNode = relNode;
+        }
+
+        private boolean betterThan(JoinPlan otherPlan) {
+            RelMetadataQuery mq = this.relNode.getCluster().getMetadataQuery();
+            RelOptCost thisCost = mq.getCumulativeCost(this.relNode);
+            RelOptCost otherCost = mq.getCumulativeCost(otherPlan.relNode);
+            if (thisCost == null || otherCost == null) {
+                return false;
+            } else {
+                return thisCost.isLt(otherCost);
+            }
+        }
+    }
+
+    /**
+     * This class is used to convert the rexInputRef index of the join condition before building a
+     * new join.
+     */
+    private static class RexInputConverterForBushyJoin extends RexShuttle {
+        private final RexBuilder rexBuilder;
+        private final LoptMultiJoin multiJoin;
+        private final List<Integer> leftFactorIds;
+        private final List<Integer> rightFactorIds;
+
+        public RexInputConverterForBushyJoin(

Review Comment:
   JoinCondititionShuffle



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);

Review Comment:
   it's more easy to debug that assign the result of `getBestPlan(lastLevelOfInnerJoin)` to a variable



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    /**
+     * Creates the topmost projection that will sit on top of the selected join ordering. The
+     * projection needs to match the original join ordering. Also, places any post-join filters on
+     * top of the project.
+     */
+    private static RelNode creatTopProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.clear();
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    /** Found possible join plans for the next level based on the found plans in the prev levels. */
+    private static Map<Set<Integer>, JoinPlan> foundNextLevel(
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> foundPlans,
+            LoptMultiJoin multiJoin) {
+        Map<Set<Integer>, JoinPlan> currentLevelJoinPlanMap = new LinkedHashMap<>();
+        int foundPlansLevel = foundPlans.size() - 1;
+        int joinLeftSideLevel = 0;
+        int joinRightSideLevel = foundPlansLevel;
+        while (joinLeftSideLevel <= joinRightSideLevel) {
+            List<JoinPlan> joinLeftSidePlans =
+                    new ArrayList<>(foundPlans.get(joinLeftSideLevel).values());
+            int planSize = joinLeftSidePlans.size();
+            for (int i = 0; i < planSize; i++) {
+                JoinPlan joinLeftSidePlan = joinLeftSidePlans.get(i);
+                List<JoinPlan> joinRightSidePlans;
+                if (joinLeftSideLevel == joinRightSideLevel) {
+                    // If left side level number equals right side level number. We can remove those
+                    // top 'i' plans which already judged in right side plans to decrease search
+                    // spaces.
+                    joinRightSidePlans = new ArrayList<>(joinLeftSidePlans);
+                    if (i > 0) {
+                        joinRightSidePlans.subList(0, i).clear();
+                    }
+                } else {
+                    joinRightSidePlans =
+                            new ArrayList<>(foundPlans.get(joinRightSideLevel).values());
+                }
+                for (JoinPlan joinRightSidePlan : joinRightSidePlans) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildInnerJoin(
+                                    relBuilder, joinLeftSidePlan, joinRightSidePlan, multiJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlanInCurrentLevel =
+                                currentLevelJoinPlanMap.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlanInCurrentLevel == null
+                                || newJoinPlan.get().betterThan(existingPlanInCurrentLevel)) {
+                            currentLevelJoinPlanMap.put(
+                                    newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            joinLeftSideLevel += 1;
+            joinRightSideLevel = foundPlansLevel - joinLeftSideLevel;

Review Comment:
   joinLeftSideLevel++;
   joinRightSideLevel--;
   



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    /**
+     * Creates the topmost projection that will sit on top of the selected join ordering. The
+     * projection needs to match the original join ordering. Also, places any post-join filters on
+     * top of the project.
+     */
+    private static RelNode creatTopProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.clear();
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    /** Found possible join plans for the next level based on the found plans in the prev levels. */
+    private static Map<Set<Integer>, JoinPlan> foundNextLevel(
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> foundPlans,
+            LoptMultiJoin multiJoin) {
+        Map<Set<Integer>, JoinPlan> currentLevelJoinPlanMap = new LinkedHashMap<>();
+        int foundPlansLevel = foundPlans.size() - 1;
+        int joinLeftSideLevel = 0;
+        int joinRightSideLevel = foundPlansLevel;
+        while (joinLeftSideLevel <= joinRightSideLevel) {
+            List<JoinPlan> joinLeftSidePlans =
+                    new ArrayList<>(foundPlans.get(joinLeftSideLevel).values());
+            int planSize = joinLeftSidePlans.size();
+            for (int i = 0; i < planSize; i++) {
+                JoinPlan joinLeftSidePlan = joinLeftSidePlans.get(i);
+                List<JoinPlan> joinRightSidePlans;
+                if (joinLeftSideLevel == joinRightSideLevel) {
+                    // If left side level number equals right side level number. We can remove those
+                    // top 'i' plans which already judged in right side plans to decrease search
+                    // spaces.
+                    joinRightSidePlans = new ArrayList<>(joinLeftSidePlans);
+                    if (i > 0) {
+                        joinRightSidePlans.subList(0, i).clear();
+                    }
+                } else {
+                    joinRightSidePlans =
+                            new ArrayList<>(foundPlans.get(joinRightSideLevel).values());
+                }
+                for (JoinPlan joinRightSidePlan : joinRightSidePlans) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildInnerJoin(
+                                    relBuilder, joinLeftSidePlan, joinRightSidePlan, multiJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlanInCurrentLevel =
+                                currentLevelJoinPlanMap.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlanInCurrentLevel == null
+                                || newJoinPlan.get().betterThan(existingPlanInCurrentLevel)) {
+                            currentLevelJoinPlanMap.put(
+                                    newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            joinLeftSideLevel += 1;
+            joinRightSideLevel = foundPlansLevel - joinLeftSideLevel;
+        }
+        return currentLevelJoinPlanMap;
+    }
+
+    private static Optional<JoinPlan> buildInnerJoin(
+            RelBuilder relBuilder,
+            JoinPlan leftSidePlan,
+            JoinPlan rightSidePlan,
+            LoptMultiJoin multiJoin) {
+        // Intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(leftSidePlan.factorIds);
+        resSet.retainAll(rightSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                getConditionsAndJoinType(
+                        leftSidePlan.factorIds, rightSidePlan.factorIds, multiJoin, false);
+        if (!joinConditions.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConditions.get().f0;
+        JoinRelType joinType = joinConditions.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan newLeftSidePlan;
+        JoinPlan newRightSidePlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (leftSidePlan.factorIds.size() >= rightSidePlan.factorIds.size()) {
+            newLeftSidePlan = leftSidePlan;
+            newRightSidePlan = rightSidePlan;
+        } else {
+            newLeftSidePlan = rightSidePlan;
+            newRightSidePlan = leftSidePlan;
+        }
+        newFactorIds.addAll(newLeftSidePlan.factorIds);
+        newFactorIds.addAll(newRightSidePlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(newLeftSidePlan.factorIds),
+                        new ArrayList<>(newRightSidePlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        relBuilder.clear();
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(newLeftSidePlan.relNode)
+                                .push(newRightSidePlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        return Optional.of(new JoinPlan(newFactorIds, newJoin));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftFactorIds,
+            List<Integer> rightFactorIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBushyJoin(
+                                        rexBuilder, multiJoin, leftFactorIds, rightFactorIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneFactorIds,
+            Set<Integer> otherFactorIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneFactorIds.size() + otherFactorIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneFactorNumbers = 0;
+                int otherFactorNumbers = 0;
+                for (int oneFactorId : oneFactorIds) {
+                    if (factorsRefByJoinFilter.get(oneFactorId)) {
+                        oneFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneFactorId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherFactorId : otherFactorIds) {
+                    if (factorsRefByJoinFilter.get(otherFactorId)) {
+                        otherFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherFactorId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneFactorNumbers > 0
+                        && otherFactorNumbers > 0
+                        && oneFactorNumbers + otherFactorNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> factorIds;
+        final RelNode relNode;
+
+        JoinPlan(LinkedHashSet<Integer> factorIds, RelNode relNode) {
+            this.factorIds = factorIds;
+            this.relNode = relNode;
+        }
+
+        private boolean betterThan(JoinPlan otherPlan) {
+            RelMetadataQuery mq = this.relNode.getCluster().getMetadataQuery();
+            RelOptCost thisCost = mq.getCumulativeCost(this.relNode);
+            RelOptCost otherCost = mq.getCumulativeCost(otherPlan.relNode);
+            if (thisCost == null || otherCost == null) {
+                return false;
+            } else {
+                return thisCost.isLt(otherCost);
+            }
+        }
+    }
+
+    /**
+     * This class is used to convert the rexInputRef index of the join condition before building a
+     * new join.
+     */
+    private static class RexInputConverterForBushyJoin extends RexShuttle {
+        private final RexBuilder rexBuilder;
+        private final LoptMultiJoin multiJoin;
+        private final List<Integer> leftFactorIds;
+        private final List<Integer> rightFactorIds;
+
+        public RexInputConverterForBushyJoin(
+                RexBuilder rexBuilder,
+                LoptMultiJoin multiJoin,
+                List<Integer> leftFactorIds,
+                List<Integer> rightFactorIds) {
+            this.rexBuilder = rexBuilder;
+            this.multiJoin = multiJoin;
+            this.leftFactorIds = leftFactorIds;
+            this.rightFactorIds = rightFactorIds;
+        }
+
+        @Override
+        public RexNode visitInputRef(RexInputRef var) {
+            int index = var.getIndex();
+            int currentIndex = 0;
+            int factorRef = multiJoin.findRef(index);
+            if (leftFactorIds.contains(factorRef)) {
+                for (Integer leftFactorId : leftFactorIds) {
+                    if (leftFactorId == factorRef) {
+                        currentIndex += findFactorIndex(index, multiJoin);
+                        return new RexInputRef(currentIndex, var.getType());
+                    } else {
+                        currentIndex += multiJoin.getNumFieldsInJoinFactor(leftFactorId);
+                    }
+                }
+            } else {
+                for (int leftFactor : leftFactorIds) {
+                    currentIndex += multiJoin.getNumFieldsInJoinFactor(leftFactor);
+                }
+                for (Integer rightFactorId : rightFactorIds) {
+                    if (rightFactorId == factorRef) {
+                        currentIndex += findFactorIndex(index, multiJoin);
+                        return rexBuilder.makeInputRef(var.getType(), currentIndex);

Review Comment:
    return new RexInputRef(currentIndex, var.getType());



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)

Review Comment:
   Why is it always `JoinRelType.LEFT` here ?



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    /**
+     * Creates the topmost projection that will sit on top of the selected join ordering. The
+     * projection needs to match the original join ordering. Also, places any post-join filters on
+     * top of the project.
+     */
+    private static RelNode creatTopProject(

Review Comment:
   createTopProject



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);

Review Comment:
   can we add some check about the remaining joins are cross join
   
   nit: these code can be extract as a method, which can be used in `addToTopForOuterJoin`



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/stream/sql/join/JoinReorderITCase.java:
##########
@@ -0,0 +1,465 @@
+/*
+ * 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.runtime.stream.sql.join;
+
+import org.apache.flink.api.common.typeinfo.TypeInformation;
+import org.apache.flink.streaming.api.datastream.DataStreamUtils;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import org.apache.flink.streaming.api.functions.sink.SinkFunction;
+import org.apache.flink.table.api.Table;
+import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatistics;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataBase;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataLong;
+import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
+import org.apache.flink.table.planner.factories.TestValuesTableFactory;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinITCaseHelper;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinType;
+import org.apache.flink.table.planner.runtime.utils.TestData;
+import org.apache.flink.table.planner.runtime.utils.TestingRetractSink;
+import org.apache.flink.table.planner.utils.JavaScalaConversionUtil;
+import org.apache.flink.test.util.TestBaseUtils;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.CollectionUtil;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import scala.Enumeration;
+import scala.Tuple2;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * ITCase for JoinReorder in stream mode. This class will test {@link
+ * org.apache.flink.table.planner.plan.rules.logical.FlinkBushyJoinReorderRule} and {@link
+ * org.apache.calcite.rel.rules.LoptOptimizeJoinRule} together by changing the factor
+ * isBushyJoinReorder.
+ */
+@RunWith(Parameterized.class)
+public class JoinReorderITCase extends TestLogger {
+
+    private StreamExecutionEnvironment env;
+    private StreamTableEnvironment tEnv;
+    private Catalog catalog;
+
+    @Parameterized.Parameter(value = 0)
+    public Enumeration.Value expectedJoinType;
+
+    @Parameterized.Parameter(value = 1)
+    public boolean isBushyJoinReorder;
+
+    @Parameterized.Parameters(name = "expectedJoinType={0}, isBushyJoinReorder={1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[][] {
+                    {JoinType.BroadcastHashJoin(), false},
+                    {JoinType.BroadcastHashJoin(), true},
+                    {JoinType.HashJoin(), false},
+                    {JoinType.HashJoin(), true},
+                    {JoinType.SortMergeJoin(), false},
+                    {JoinType.SortMergeJoin(), true},
+                    {JoinType.NestedLoopJoin(), false},
+                    {JoinType.NestedLoopJoin(), true}
+                });
+    }
+
+    @Before
+    public void before() throws Exception {
+        env = StreamExecutionEnvironment.getExecutionEnvironment();
+        tEnv = StreamTableEnvironment.create(env);
+        catalog = tEnv.getCatalog(tEnv.getCurrentCatalog()).get();
+
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(ExecutionConfigOptions.TABLE_EXEC_RESOURCE_DEFAULT_PARALLELISM, 1);
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(OptimizerConfigOptions.TABLE_OPTIMIZER_JOIN_REORDER_ENABLED, true);
+
+        if (!isBushyJoinReorder) {
+            tEnv.getConfig()
+                    .getConfiguration()
+                    .set(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD, 3);
+        }

Review Comment:
   else {
    tEnv.getConfig()
                       .getConfiguration()
                       .set(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD, 1000);
   }



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/stream/sql/join/JoinReorderITCase.java:
##########
@@ -0,0 +1,465 @@
+/*
+ * 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.runtime.stream.sql.join;
+
+import org.apache.flink.api.common.typeinfo.TypeInformation;
+import org.apache.flink.streaming.api.datastream.DataStreamUtils;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import org.apache.flink.streaming.api.functions.sink.SinkFunction;
+import org.apache.flink.table.api.Table;
+import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatistics;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataBase;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataLong;
+import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
+import org.apache.flink.table.planner.factories.TestValuesTableFactory;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinITCaseHelper;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinType;
+import org.apache.flink.table.planner.runtime.utils.TestData;
+import org.apache.flink.table.planner.runtime.utils.TestingRetractSink;
+import org.apache.flink.table.planner.utils.JavaScalaConversionUtil;
+import org.apache.flink.test.util.TestBaseUtils;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.CollectionUtil;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import scala.Enumeration;
+import scala.Tuple2;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * ITCase for JoinReorder in stream mode. This class will test {@link
+ * org.apache.flink.table.planner.plan.rules.logical.FlinkBushyJoinReorderRule} and {@link
+ * org.apache.calcite.rel.rules.LoptOptimizeJoinRule} together by changing the factor
+ * isBushyJoinReorder.
+ */
+@RunWith(Parameterized.class)
+public class JoinReorderITCase extends TestLogger {
+
+    private StreamExecutionEnvironment env;
+    private StreamTableEnvironment tEnv;
+    private Catalog catalog;
+
+    @Parameterized.Parameter(value = 0)
+    public Enumeration.Value expectedJoinType;
+
+    @Parameterized.Parameter(value = 1)
+    public boolean isBushyJoinReorder;
+
+    @Parameterized.Parameters(name = "expectedJoinType={0}, isBushyJoinReorder={1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[][] {
+                    {JoinType.BroadcastHashJoin(), false},
+                    {JoinType.BroadcastHashJoin(), true},
+                    {JoinType.HashJoin(), false},
+                    {JoinType.HashJoin(), true},
+                    {JoinType.SortMergeJoin(), false},
+                    {JoinType.SortMergeJoin(), true},
+                    {JoinType.NestedLoopJoin(), false},
+                    {JoinType.NestedLoopJoin(), true}
+                });
+    }
+
+    @Before
+    public void before() throws Exception {
+        env = StreamExecutionEnvironment.getExecutionEnvironment();
+        tEnv = StreamTableEnvironment.create(env);
+        catalog = tEnv.getCatalog(tEnv.getCurrentCatalog()).get();
+
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(ExecutionConfigOptions.TABLE_EXEC_RESOURCE_DEFAULT_PARALLELISM, 1);
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(OptimizerConfigOptions.TABLE_OPTIMIZER_JOIN_REORDER_ENABLED, true);
+
+        if (!isBushyJoinReorder) {
+            tEnv.getConfig()
+                    .getConfiguration()
+                    .set(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD, 3);
+        }
+
+        JoinITCaseHelper.disableOtherJoinOpForJoin(tEnv, expectedJoinType);
+
+        // Test data
+        String dataId2 = TestValuesTableFactory.registerData(TestData.data2());
+        tEnv.executeSql(
+                String.format(
+                        "CREATE TABLE T1 (\n"
+                                + "  a1 INT,\n"
+                                + "  b1 BIGINT,\n"
+                                + "  c1 INT,\n"
+                                + "  d1 STRING,\n"
+                                + "  e1 BIGINT\n"
+                                + ")  WITH (\n"
+                                + " 'connector' = 'values',\n"
+                                + " 'data-id' = '%s',\n"
+                                + " 'bounded' = 'false'\n"
+                                + ")",
+                        dataId2));
+        catalog.alterTableStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T1"),
+                new CatalogTableStatistics(100000, 1, 1, 1),
+                false);
+
+        tEnv.executeSql(
+                String.format(
+                        "CREATE TABLE T2 (\n"
+                                + "  a2 INT,\n"
+                                + "  b2 BIGINT,\n"
+                                + "  c2 INT,\n"
+                                + "  d2 STRING,\n"
+                                + "  e2 BIGINT\n"
+                                + ")  WITH (\n"
+                                + " 'connector' = 'values',\n"
+                                + " 'data-id' = '%s',\n"
+                                + " 'bounded' = 'false'\n"
+                                + ")",
+                        dataId2));
+        catalog.alterTableStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T2"),
+                new CatalogTableStatistics(10000, 1, 1, 1),
+                false);
+
+        String dataId3 = TestValuesTableFactory.registerData(TestData.smallData3());
+        tEnv.executeSql(
+                String.format(
+                        "CREATE TABLE T3 (\n"
+                                + "  a3 INT,\n"
+                                + "  b3 BIGINT,\n"
+                                + "  c3 STRING\n"
+                                + ")  WITH (\n"
+                                + " 'connector' = 'values',\n"
+                                + " 'data-id' = '%s',\n"
+                                + " 'bounded' = 'false'\n"
+                                + ")",
+                        dataId3));
+        catalog.alterTableStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T3"),
+                new CatalogTableStatistics(1000, 1, 1, 1),
+                false);
+
+        String dataId5 = TestValuesTableFactory.registerData(TestData.data5());
+        tEnv.executeSql(
+                String.format(
+                        "CREATE TABLE T4 (\n"
+                                + "  a4 INT,\n"
+                                + "  b4 BIGINT,\n"
+                                + "  c4 INT,\n"
+                                + "  d4 STRING,\n"
+                                + "  e4 BIGINT\n"
+                                + ")  WITH (\n"
+                                + " 'connector' = 'values',\n"
+                                + " 'data-id' = '%s',\n"
+                                + " 'bounded' = 'false'\n"
+                                + ")",
+                        dataId5));
+        catalog.alterTableStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T4"),
+                new CatalogTableStatistics(100, 1, 1, 1),
+                false);
+    }
+
+    @Test
+    public void testJoinReorderWithFullOuterJoin() throws Exception {
+        if (expectedJoinType == JoinType.BroadcastHashJoin()) {
+            return;
+        }
+
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "FULL OUTER JOIN T3 ON T4.b4 = T3.b3 "
+                        + "FULL OUTER JOIN T2 ON T4.b4 = T2.b2 "
+                        + "FULL OUTER JOIN T1 ON T2.b2 = T1.b1";
+
+        List<String> expectedList =
+                Arrays.asList(
+                        "ABC,null,ABC,ABC",
+                        "BCD,null,BCD,BCD",
+                        "CDE,null,CDE,CDE",
+                        "DEF,null,DEF,DEF",
+                        "EFG,null,EFG,EFG",
+                        "FGH,null,FGH,FGH",
+                        "GHI,null,GHI,GHI",
+                        "HIJ,null,HIJ,HIJ",
+                        "Hallo Welt wie gehts?,null,Hallo Welt wie gehts?,Hallo Welt wie gehts?",
+                        "Hallo Welt wie,null,Hallo Welt wie,Hallo Welt wie",
+                        "Hallo Welt,Hello world,Hallo Welt,Hallo Welt",
+                        "Hallo Welt,Hello,Hallo Welt,Hallo Welt",
+                        "Hallo,Hi,Hallo,Hallo",
+                        "IJK,null,IJK,IJK",
+                        "JKL,null,JKL,JKL",
+                        "KLM,null,KLM,KLM");
+        assertEquals(query, expectedList);
+    }
+
+    @Test
+    public void testJoinReorderWithInnerAndFullOuterJoin() throws Exception {
+        if (expectedJoinType == JoinType.BroadcastHashJoin()) {
+            return;
+        }
+
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "JOIN T3 ON T4.b4 = T3.b3 "
+                        + "FULL OUTER JOIN T2 ON T4.b4 = T2.b2 "
+                        + "JOIN T1 ON T4.b4 = T1.b1";
+
+        List<String> expectedList =
+                Arrays.asList(
+                        "Hallo Welt,Hello world,Hallo Welt,Hallo Welt",
+                        "Hallo Welt,Hello,Hallo Welt,Hallo Welt",
+                        "Hallo,Hi,Hallo,Hallo");
+        assertEquals(query, expectedList);
+    }
+
+    @Test
+    public void testJoinReorderWithInnerJoin() {
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "JOIN T3 ON T4.b4 = T3.b3 "
+                        + "JOIN T2 ON T4.b4 = T2.b2 "
+                        + "JOIN T1 ON T4.b4 = T1.b1 WHERE T1.a1 > 0 AND T3.a3 > 0";
+        List<Row> results =
+                CollectionUtil.iteratorToList(
+                        DataStreamUtils.collect(
+                                tEnv.toAppendStream(tEnv.sqlQuery(query), Row.class)));
+        String expected =
+                "+I[Hallo Welt, Hello, Hallo Welt, Hallo Welt]\n"
+                        + "+I[Hallo Welt, Hello world, Hallo Welt, Hallo Welt]\n"
+                        + "+I[Hallo, Hi, Hallo, Hallo]";
+        TestBaseUtils.compareResultAsText(results, expected);
+    }
+
+    @Test
+    public void testJoinReorderWithLeftOuterJoin() throws Exception {
+        // can reorder, all join keys will not generate null.
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "LEFT OUTER JOIN T3 ON T4.b4 = T3.b3 "
+                        + "LEFT OUTER JOIN T2 ON T4.b4 = T2.b2 "
+                        + "LEFT OUTER JOIN T1 ON T4.b4 = T1.b1 WHERE T4.a4 < 3";
+
+        List<String> expectedList =
+                Arrays.asList(
+                        "Hallo,Hi,Hallo,Hallo",
+                        "Hallo Welt,Hello,Hallo Welt,Hallo Welt",
+                        "Hallo Welt,Hello world,Hallo Welt,Hallo Welt",
+                        "Hallo Welt wie,null,Hallo Welt wie,Hallo Welt wie");
+        assertEquals(query, expectedList);
+    }
+
+    @Test
+    public void testJoinReorderWithInnerAndLeftOuterJoin() throws Exception {
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "JOIN T3 ON T4.b4 = T3.b3 "
+                        + "LEFT OUTER JOIN T2 ON T4.b4 = T2.b2 "
+                        + "JOIN T1 ON T4.b4 = T1.b1 "
+                        + "WHERE T4.a4 < 3";
+
+        List<String> expectedList =
+                Arrays.asList(
+                        "Hallo,Hi,Hallo,Hallo",
+                        "Hallo Welt,Hello,Hallo Welt,Hallo Welt",
+                        "Hallo Welt,Hello world,Hallo Welt,Hallo Welt");
+        assertEquals(query, expectedList);
+    }
+
+    @Test
+    public void testJoinReorderWithRightOuterJoin() throws Exception {
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "RIGHT OUTER JOIN T3 ON T4.b4 = T3.b3 "
+                        + "JOIN T2 ON T3.b3 = T2.b2 "
+                        + "JOIN T1 ON T2.b2 = T1.b1 WHERE T2.a2 <= 2";
+
+        List<String> expectedList =
+                Arrays.asList(
+                        "Hallo,Hi,Hallo,Hallo",
+                        "Hallo Welt,Hello,Hallo Welt,Hallo Welt",
+                        "Hallo Welt,Hello world,Hallo Welt,Hallo Welt");
+        assertEquals(query, expectedList);
+    }
+
+    @Test
+    public void testJoinReorderWithTrueCondition() {
+        // Only supports NestedLoopJoin.
+        if (expectedJoinType != JoinType.NestedLoopJoin()) {
+            return;
+        }
+
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4, T3, T2, T1 "
+                        + "WHERE T4.a4 <= 1 AND T3.a3 <= 1 AND T2.a2 <= 1 AND T1.a1 <= 1";
+        List<Row> results =
+                CollectionUtil.iteratorToList(
+                        DataStreamUtils.collect(
+                                tEnv.toAppendStream(tEnv.sqlQuery(query), Row.class)));
+        String expected = "+I[Hallo, Hi, Hallo, Hallo]";
+        TestBaseUtils.compareResultAsText(results, expected);
+    }
+
+    @Test
+    public void testJoinReorderWithInnerJoinAndTrueCondition() {
+        // Only supports NestedLoopJoin.
+        if (expectedJoinType != JoinType.NestedLoopJoin()) {
+            return;
+        }
+
+        String query =
+                "SELECT tab1.d4, tab1.c3, T2.d2, T1.d1 FROM T1, "
+                        + "(SELECT * FROM T3 JOIN T4 ON T4.b4 = T3.b3) tab1, T2 "
+                        + "WHERE tab1.a4 <= 1 AND tab1.a3 <= 1 AND T2.a2 <= 1 AND T1.a1 <= 1";
+        List<Row> results =
+                CollectionUtil.iteratorToList(
+                        DataStreamUtils.collect(
+                                tEnv.toAppendStream(tEnv.sqlQuery(query), Row.class)));
+        String expected = "+I[Hallo, Hi, Hallo, Hallo]";
+        TestBaseUtils.compareResultAsText(results, expected);
+    }
+
+    @Test
+    public void testJoinReorderWithMixedJoinTypeAndCondition() {
+        // Only supports NestedLoopJoin.
+        if (expectedJoinType != JoinType.NestedLoopJoin()) {
+            return;
+        }
+
+        String query =
+                "SELECT tab2.d4, tab2.c3, tab2.d2, T1.d1 FROM T1, (SELECT * FROM T4 "
+                        + "LEFT OUTER JOIN T3 ON T4.b4 = T3.b3 "
+                        + "JOIN T2 ON T4.b4 = T2.b2) tab2 "
+                        + "WHERE tab2.a4 <= 1 AND tab2.a3 <= 1 AND tab2.a2 <= 1 AND T1.a1 <= 1";
+        List<Row> results =
+                CollectionUtil.iteratorToList(
+                        DataStreamUtils.collect(
+                                tEnv.toAppendStream(tEnv.sqlQuery(query), Row.class)));
+        String expected = "+I[Hallo, Hi, Hallo, Hallo]";
+        TestBaseUtils.compareResultAsText(results, expected);
+    }
+
+    @Test
+    public void testBusyTreeJoinReorder() throws Exception {
+        CatalogColumnStatisticsDataLong longColStats =
+                new CatalogColumnStatisticsDataLong(100L, 100L, 50L, 1000L);
+        Map<String, CatalogColumnStatisticsDataBase> colStatsMap = new HashMap<>(1);
+        colStatsMap.put("b1", longColStats);
+        catalog.alterTableColumnStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T1"),
+                new CatalogColumnStatistics(colStatsMap),
+                false);
+
+        longColStats = new CatalogColumnStatisticsDataLong(100L, 100L, 500000L, 1000L);
+        colStatsMap = new HashMap<>(1);
+        colStatsMap.put("b2", longColStats);
+        catalog.alterTableColumnStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T2"),
+                new CatalogColumnStatistics(colStatsMap),
+                false);
+
+        longColStats = new CatalogColumnStatisticsDataLong(100L, 100L, 50L, 1000L);
+        colStatsMap = new HashMap<>(1);
+        colStatsMap.put("b3", longColStats);
+        catalog.alterTableColumnStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T3"),
+                new CatalogColumnStatistics(colStatsMap),
+                false);
+
+        longColStats = new CatalogColumnStatisticsDataLong(100L, 100L, 500000L, 1000L);
+        colStatsMap = new HashMap<>(1);
+        colStatsMap.put("b4", longColStats);
+        catalog.alterTableColumnStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T4"),
+                new CatalogColumnStatistics(colStatsMap),
+                false);
+
+        String query =
+                "SELECT tab2.d4, tab2.c3, tab1.d2, tab1.d1 FROM "
+                        + "(SELECT * FROM T1 JOIN T2 ON T1.b1 = T2.b2) tab1 "
+                        + "JOIN (SELECT * FROM T3 JOIN T4 ON T3.b3 = T4.b4) tab2 "
+                        + "ON tab1.b2 = tab2.b4";
+
+        List<Row> results =
+                CollectionUtil.iteratorToList(
+                        DataStreamUtils.collect(
+                                tEnv.toAppendStream(tEnv.sqlQuery(query), Row.class)));
+        String expected =
+                "+I[Hallo Welt, Hello, Hallo Welt, Hallo Welt]\n"
+                        + "+I[Hallo Welt, Hello world, Hallo Welt, Hallo Welt]\n"
+                        + "+I[Hallo, Hi, Hallo, Hallo]";
+        TestBaseUtils.compareResultAsText(results, expected);
+    }
+
+    @Test
+    public void testBushyJoinReorderDpThreshold() throws Exception {

Review Comment:
   this test is unnecessary, because it can be verified via plan test



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/stream/sql/join/JoinReorderITCase.java:
##########
@@ -0,0 +1,465 @@
+/*
+ * 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.runtime.stream.sql.join;
+
+import org.apache.flink.api.common.typeinfo.TypeInformation;
+import org.apache.flink.streaming.api.datastream.DataStreamUtils;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import org.apache.flink.streaming.api.functions.sink.SinkFunction;
+import org.apache.flink.table.api.Table;
+import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatistics;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataBase;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataLong;
+import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
+import org.apache.flink.table.planner.factories.TestValuesTableFactory;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinITCaseHelper;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinType;
+import org.apache.flink.table.planner.runtime.utils.TestData;
+import org.apache.flink.table.planner.runtime.utils.TestingRetractSink;
+import org.apache.flink.table.planner.utils.JavaScalaConversionUtil;
+import org.apache.flink.test.util.TestBaseUtils;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.CollectionUtil;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import scala.Enumeration;
+import scala.Tuple2;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * ITCase for JoinReorder in stream mode. This class will test {@link
+ * org.apache.flink.table.planner.plan.rules.logical.FlinkBushyJoinReorderRule} and {@link
+ * org.apache.calcite.rel.rules.LoptOptimizeJoinRule} together by changing the factor
+ * isBushyJoinReorder.
+ */
+@RunWith(Parameterized.class)
+public class JoinReorderITCase extends TestLogger {
+
+    private StreamExecutionEnvironment env;
+    private StreamTableEnvironment tEnv;
+    private Catalog catalog;
+
+    @Parameterized.Parameter(value = 0)
+    public Enumeration.Value expectedJoinType;
+
+    @Parameterized.Parameter(value = 1)
+    public boolean isBushyJoinReorder;
+
+    @Parameterized.Parameters(name = "expectedJoinType={0}, isBushyJoinReorder={1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[][] {
+                    {JoinType.BroadcastHashJoin(), false},
+                    {JoinType.BroadcastHashJoin(), true},
+                    {JoinType.HashJoin(), false},
+                    {JoinType.HashJoin(), true},
+                    {JoinType.SortMergeJoin(), false},
+                    {JoinType.SortMergeJoin(), true},
+                    {JoinType.NestedLoopJoin(), false},
+                    {JoinType.NestedLoopJoin(), true}

Review Comment:
   there are no broadcast join, hash join, sort merge join, and nested loop join for streaming



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(

Review Comment:
   addOuterJoinToTop



##########
flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/common/JoinReorderTestBase.scala:
##########
@@ -121,6 +121,8 @@ abstract class JoinReorderTestBase extends TableTestBase {
 
     util.getTableEnv.getConfig
       .set(OptimizerConfigOptions.TABLE_OPTIMIZER_JOIN_REORDER_ENABLED, Boolean.box(true))
+    util.getTableEnv.getConfig

Review Comment:
   we should rename `JoinReorderTestBase` to LeftDeepJoinReorderTestBase



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(

Review Comment:
   addCrossJoinToTop



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on PR #21530:
URL: https://github.com/apache/flink/pull/21530#issuecomment-1381249294

   @flinkbot run azure


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink busy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1058025322


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBusyJoinReorderRule.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+import org.apache.flink.table.planner.plan.utils.FlinkRelOptUtil;
+import org.apache.flink.table.planner.utils.ShortcutUtils;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink busy join reorder rule, which will convert {@link MultiJoin} to a busy join tree.
+ *
+ * <p>In this busy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBusyJoinReorderRule extends RelRule<FlinkBusyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    public static final LoptOptimizeJoinRule MULTI_JOIN_OPTIMIZE =
+            LoptOptimizeJoinRule.Config.DEFAULT.toRule();
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBusyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        Boolean enableBusyJoin =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER);
+        Integer dpThreshold =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER_DP_THRESHOLD);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+
+        if (enableBusyJoin && numJoinFactors <= dpThreshold) {
+            RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+            call.transformTo(bestOrder);
+        } else {
+            MULTI_JOIN_OPTIMIZE.onMatch(call);
+        }
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our busy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                RelOptCost cost = mq.getCumulativeCost(joinFactor);
+                joinPlanMap.put(
+                        set1,
+                        new JoinPlan(
+                                set2,
+                                joinFactor,
+                                new FlinkCost(
+                                        cost.getRows(), cost.getCpu(), cost.getIo(), 0.0, 0.0)));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().cost.isLt(bestPlan.cost)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.itemIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.itemIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, List<JoinPlan>> printNextLevel = new LinkedHashMap<>();
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    mq,
+                                    relBuilder,
+                                    oneSidePlan,
+                                    otherSidePlan,
+                                    multiJoin,
+                                    isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().itemIds);
+                        // check if it's the first plan for the item set, or it's a better plan than
+                        // the existing one due to lower cost.
+                        if (existingPlan == null || newJoinPlan.get().betterThan(existingPlan)) {
+                            nextLevel.put(newJoinPlan.get().itemIds, newJoinPlan.get());
+                        }
+
+                        if (printNextLevel.get(newJoinPlan.get().itemIds) == null) {
+                            printNextLevel.put(
+                                    newJoinPlan.get().itemIds,
+                                    Collections.singletonList(newJoinPlan.get()));
+                        } else {
+                            List<JoinPlan> joinPlans =
+                                    new ArrayList<>(printNextLevel.get(newJoinPlan.get().itemIds));
+                            joinPlans.add(newJoinPlan.get());
+                            printNextLevel.put(newJoinPlan.get().itemIds, joinPlans);
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+
+        // print
+        for (Map.Entry<Set<Integer>, List<JoinPlan>> entry : printNextLevel.entrySet()) {
+            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++");
+            System.out.printf("item sets: %s%n", entry.getKey());
+            for (JoinPlan joinPlan : entry.getValue()) {
+                System.out.println("--------------------------------------------");
+                System.out.printf("costs: %s%n", joinPlan.cost.getRows());
+                System.out.println(
+                        FlinkRelOptUtil.toString(
+                                joinPlan.relNode,
+                                SqlExplainLevel.ALL_ATTRIBUTES,
+                                false,
+                                false,
+                                false,
+                                false,
+                                false));
+                System.out.println("--------------------------------------------");
+            }
+            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++");
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping item sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.itemIds);
+        resSet.retainAll(otherSidePlan.itemIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.itemIds, otherSidePlan.itemIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newItemIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.itemIds.size() >= otherSidePlan.itemIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newItemIds.addAll(leftPlan.itemIds);
+        newItemIds.addAll(rightPlan.itemIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.itemIds),
+                        new ArrayList<>(rightPlan.itemIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(leftPlan.relNode)
+                                .push(rightPlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        RelOptCost cost = mq.getCumulativeCost(newJoin);
+        return Optional.of(
+                new JoinPlan(
+                        newItemIds,
+                        newJoin,
+                        new FlinkCost(cost.getRows(), cost.getCpu(), cost.getIo(), 0.0, 0.0)));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftItemIds,
+            List<Integer> rightItemIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBusyJoin(
+                                        rexBuilder, multiJoin, leftItemIds, rightItemIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneItemIds,
+            Set<Integer> otherItemIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneItemIds.size() + otherItemIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneItemNumbers = 0;
+                int otherItemNumbers = 0;
+                for (int oneItemId : oneItemIds) {
+                    if (factorsRefByJoinFilter.get(oneItemId)) {
+                        oneItemNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneItemId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherItemId : otherItemIds) {
+                    if (factorsRefByJoinFilter.get(otherItemId)) {
+                        otherItemNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherItemId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneItemNumbers > 0
+                        && otherItemNumbers > 0
+                        && oneItemNumbers + otherItemNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> itemIds;
+        final RelNode relNode;
+        final FlinkCost cost;

Review Comment:
   > the cost can be recompute via `mq.getCumulativeCost`, this cost is used just for caching to speed the computation up ?
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink busy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1058019619


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBusyJoinReorderRule.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+import org.apache.flink.table.planner.plan.utils.FlinkRelOptUtil;
+import org.apache.flink.table.planner.utils.ShortcutUtils;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink busy join reorder rule, which will convert {@link MultiJoin} to a busy join tree.
+ *
+ * <p>In this busy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBusyJoinReorderRule extends RelRule<FlinkBusyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    public static final LoptOptimizeJoinRule MULTI_JOIN_OPTIMIZE =
+            LoptOptimizeJoinRule.Config.DEFAULT.toRule();
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBusyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {

Review Comment:
   > If's better we can introduce a `FlinkJoinReorderRule`, which could choose the specific join reorder algorithm based on `dpThreshold `
   Done!
   



##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBusyJoinReorderRule.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+import org.apache.flink.table.planner.plan.utils.FlinkRelOptUtil;
+import org.apache.flink.table.planner.utils.ShortcutUtils;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink busy join reorder rule, which will convert {@link MultiJoin} to a busy join tree.
+ *
+ * <p>In this busy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBusyJoinReorderRule extends RelRule<FlinkBusyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    public static final LoptOptimizeJoinRule MULTI_JOIN_OPTIMIZE =
+            LoptOptimizeJoinRule.Config.DEFAULT.toRule();
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBusyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        Boolean enableBusyJoin =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER);
+        Integer dpThreshold =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER_DP_THRESHOLD);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+
+        if (enableBusyJoin && numJoinFactors <= dpThreshold) {
+            RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+            call.transformTo(bestOrder);
+        } else {
+            MULTI_JOIN_OPTIMIZE.onMatch(call);
+        }
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our busy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                RelOptCost cost = mq.getCumulativeCost(joinFactor);
+                joinPlanMap.put(
+                        set1,
+                        new JoinPlan(
+                                set2,
+                                joinFactor,
+                                new FlinkCost(
+                                        cost.getRows(), cost.getCpu(), cost.getIo(), 0.0, 0.0)));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().cost.isLt(bestPlan.cost)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.itemIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.itemIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, List<JoinPlan>> printNextLevel = new LinkedHashMap<>();
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    mq,
+                                    relBuilder,
+                                    oneSidePlan,
+                                    otherSidePlan,
+                                    multiJoin,
+                                    isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().itemIds);
+                        // check if it's the first plan for the item set, or it's a better plan than
+                        // the existing one due to lower cost.
+                        if (existingPlan == null || newJoinPlan.get().betterThan(existingPlan)) {
+                            nextLevel.put(newJoinPlan.get().itemIds, newJoinPlan.get());
+                        }
+
+                        if (printNextLevel.get(newJoinPlan.get().itemIds) == null) {
+                            printNextLevel.put(
+                                    newJoinPlan.get().itemIds,
+                                    Collections.singletonList(newJoinPlan.get()));
+                        } else {
+                            List<JoinPlan> joinPlans =
+                                    new ArrayList<>(printNextLevel.get(newJoinPlan.get().itemIds));
+                            joinPlans.add(newJoinPlan.get());
+                            printNextLevel.put(newJoinPlan.get().itemIds, joinPlans);
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+
+        // print
+        for (Map.Entry<Set<Integer>, List<JoinPlan>> entry : printNextLevel.entrySet()) {
+            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++");

Review Comment:
   > the `print` code can be removed
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066523057


##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/stream/sql/join/JoinReorderITCase.java:
##########
@@ -0,0 +1,465 @@
+/*
+ * 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.runtime.stream.sql.join;
+
+import org.apache.flink.api.common.typeinfo.TypeInformation;
+import org.apache.flink.streaming.api.datastream.DataStreamUtils;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import org.apache.flink.streaming.api.functions.sink.SinkFunction;
+import org.apache.flink.table.api.Table;
+import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatistics;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataBase;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataLong;
+import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
+import org.apache.flink.table.planner.factories.TestValuesTableFactory;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinITCaseHelper;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinType;
+import org.apache.flink.table.planner.runtime.utils.TestData;
+import org.apache.flink.table.planner.runtime.utils.TestingRetractSink;
+import org.apache.flink.table.planner.utils.JavaScalaConversionUtil;
+import org.apache.flink.test.util.TestBaseUtils;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.CollectionUtil;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import scala.Enumeration;
+import scala.Tuple2;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * ITCase for JoinReorder in stream mode. This class will test {@link
+ * org.apache.flink.table.planner.plan.rules.logical.FlinkBushyJoinReorderRule} and {@link
+ * org.apache.calcite.rel.rules.LoptOptimizeJoinRule} together by changing the factor
+ * isBushyJoinReorder.
+ */
+@RunWith(Parameterized.class)
+public class JoinReorderITCase extends TestLogger {
+
+    private StreamExecutionEnvironment env;
+    private StreamTableEnvironment tEnv;
+    private Catalog catalog;
+
+    @Parameterized.Parameter(value = 0)
+    public Enumeration.Value expectedJoinType;
+
+    @Parameterized.Parameter(value = 1)
+    public boolean isBushyJoinReorder;
+
+    @Parameterized.Parameters(name = "expectedJoinType={0}, isBushyJoinReorder={1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[][] {
+                    {JoinType.BroadcastHashJoin(), false},
+                    {JoinType.BroadcastHashJoin(), true},
+                    {JoinType.HashJoin(), false},
+                    {JoinType.HashJoin(), true},
+                    {JoinType.SortMergeJoin(), false},
+                    {JoinType.SortMergeJoin(), true},
+                    {JoinType.NestedLoopJoin(), false},
+                    {JoinType.NestedLoopJoin(), true}
+                });
+    }
+
+    @Before
+    public void before() throws Exception {
+        env = StreamExecutionEnvironment.getExecutionEnvironment();
+        tEnv = StreamTableEnvironment.create(env);
+        catalog = tEnv.getCatalog(tEnv.getCurrentCatalog()).get();
+
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(ExecutionConfigOptions.TABLE_EXEC_RESOURCE_DEFAULT_PARALLELISM, 1);
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(OptimizerConfigOptions.TABLE_OPTIMIZER_JOIN_REORDER_ENABLED, true);
+
+        if (!isBushyJoinReorder) {
+            tEnv.getConfig()
+                    .getConfiguration()
+                    .set(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD, 3);
+        }
+
+        JoinITCaseHelper.disableOtherJoinOpForJoin(tEnv, expectedJoinType);
+
+        // Test data
+        String dataId2 = TestValuesTableFactory.registerData(TestData.data2());
+        tEnv.executeSql(
+                String.format(
+                        "CREATE TABLE T1 (\n"
+                                + "  a1 INT,\n"
+                                + "  b1 BIGINT,\n"
+                                + "  c1 INT,\n"
+                                + "  d1 STRING,\n"
+                                + "  e1 BIGINT\n"
+                                + ")  WITH (\n"
+                                + " 'connector' = 'values',\n"
+                                + " 'data-id' = '%s',\n"
+                                + " 'bounded' = 'false'\n"
+                                + ")",
+                        dataId2));
+        catalog.alterTableStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T1"),
+                new CatalogTableStatistics(100000, 1, 1, 1),
+                false);
+
+        tEnv.executeSql(
+                String.format(
+                        "CREATE TABLE T2 (\n"
+                                + "  a2 INT,\n"
+                                + "  b2 BIGINT,\n"
+                                + "  c2 INT,\n"
+                                + "  d2 STRING,\n"
+                                + "  e2 BIGINT\n"
+                                + ")  WITH (\n"
+                                + " 'connector' = 'values',\n"
+                                + " 'data-id' = '%s',\n"
+                                + " 'bounded' = 'false'\n"
+                                + ")",
+                        dataId2));
+        catalog.alterTableStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T2"),
+                new CatalogTableStatistics(10000, 1, 1, 1),
+                false);
+
+        String dataId3 = TestValuesTableFactory.registerData(TestData.smallData3());
+        tEnv.executeSql(
+                String.format(
+                        "CREATE TABLE T3 (\n"
+                                + "  a3 INT,\n"
+                                + "  b3 BIGINT,\n"
+                                + "  c3 STRING\n"
+                                + ")  WITH (\n"
+                                + " 'connector' = 'values',\n"
+                                + " 'data-id' = '%s',\n"
+                                + " 'bounded' = 'false'\n"
+                                + ")",
+                        dataId3));
+        catalog.alterTableStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T3"),
+                new CatalogTableStatistics(1000, 1, 1, 1),
+                false);
+
+        String dataId5 = TestValuesTableFactory.registerData(TestData.data5());
+        tEnv.executeSql(
+                String.format(
+                        "CREATE TABLE T4 (\n"
+                                + "  a4 INT,\n"
+                                + "  b4 BIGINT,\n"
+                                + "  c4 INT,\n"
+                                + "  d4 STRING,\n"
+                                + "  e4 BIGINT\n"
+                                + ")  WITH (\n"
+                                + " 'connector' = 'values',\n"
+                                + " 'data-id' = '%s',\n"
+                                + " 'bounded' = 'false'\n"
+                                + ")",
+                        dataId5));
+        catalog.alterTableStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T4"),
+                new CatalogTableStatistics(100, 1, 1, 1),
+                false);
+    }
+
+    @Test
+    public void testJoinReorderWithFullOuterJoin() throws Exception {
+        if (expectedJoinType == JoinType.BroadcastHashJoin()) {
+            return;
+        }
+
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "FULL OUTER JOIN T3 ON T4.b4 = T3.b3 "
+                        + "FULL OUTER JOIN T2 ON T4.b4 = T2.b2 "
+                        + "FULL OUTER JOIN T1 ON T2.b2 = T1.b1";
+
+        List<String> expectedList =
+                Arrays.asList(
+                        "ABC,null,ABC,ABC",
+                        "BCD,null,BCD,BCD",
+                        "CDE,null,CDE,CDE",
+                        "DEF,null,DEF,DEF",
+                        "EFG,null,EFG,EFG",
+                        "FGH,null,FGH,FGH",
+                        "GHI,null,GHI,GHI",
+                        "HIJ,null,HIJ,HIJ",
+                        "Hallo Welt wie gehts?,null,Hallo Welt wie gehts?,Hallo Welt wie gehts?",
+                        "Hallo Welt wie,null,Hallo Welt wie,Hallo Welt wie",
+                        "Hallo Welt,Hello world,Hallo Welt,Hallo Welt",
+                        "Hallo Welt,Hello,Hallo Welt,Hallo Welt",
+                        "Hallo,Hi,Hallo,Hallo",
+                        "IJK,null,IJK,IJK",
+                        "JKL,null,JKL,JKL",
+                        "KLM,null,KLM,KLM");
+        assertEquals(query, expectedList);
+    }
+
+    @Test
+    public void testJoinReorderWithInnerAndFullOuterJoin() throws Exception {
+        if (expectedJoinType == JoinType.BroadcastHashJoin()) {
+            return;
+        }
+
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "JOIN T3 ON T4.b4 = T3.b3 "
+                        + "FULL OUTER JOIN T2 ON T4.b4 = T2.b2 "
+                        + "JOIN T1 ON T4.b4 = T1.b1";
+
+        List<String> expectedList =
+                Arrays.asList(
+                        "Hallo Welt,Hello world,Hallo Welt,Hallo Welt",
+                        "Hallo Welt,Hello,Hallo Welt,Hallo Welt",
+                        "Hallo,Hi,Hallo,Hallo");
+        assertEquals(query, expectedList);
+    }
+
+    @Test
+    public void testJoinReorderWithInnerJoin() {
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "JOIN T3 ON T4.b4 = T3.b3 "
+                        + "JOIN T2 ON T4.b4 = T2.b2 "
+                        + "JOIN T1 ON T4.b4 = T1.b1 WHERE T1.a1 > 0 AND T3.a3 > 0";
+        List<Row> results =
+                CollectionUtil.iteratorToList(
+                        DataStreamUtils.collect(
+                                tEnv.toAppendStream(tEnv.sqlQuery(query), Row.class)));
+        String expected =
+                "+I[Hallo Welt, Hello, Hallo Welt, Hallo Welt]\n"
+                        + "+I[Hallo Welt, Hello world, Hallo Welt, Hallo Welt]\n"
+                        + "+I[Hallo, Hi, Hallo, Hallo]";
+        TestBaseUtils.compareResultAsText(results, expected);
+    }
+
+    @Test
+    public void testJoinReorderWithLeftOuterJoin() throws Exception {
+        // can reorder, all join keys will not generate null.
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "LEFT OUTER JOIN T3 ON T4.b4 = T3.b3 "
+                        + "LEFT OUTER JOIN T2 ON T4.b4 = T2.b2 "
+                        + "LEFT OUTER JOIN T1 ON T4.b4 = T1.b1 WHERE T4.a4 < 3";
+
+        List<String> expectedList =
+                Arrays.asList(
+                        "Hallo,Hi,Hallo,Hallo",
+                        "Hallo Welt,Hello,Hallo Welt,Hallo Welt",
+                        "Hallo Welt,Hello world,Hallo Welt,Hallo Welt",
+                        "Hallo Welt wie,null,Hallo Welt wie,Hallo Welt wie");
+        assertEquals(query, expectedList);
+    }
+
+    @Test
+    public void testJoinReorderWithInnerAndLeftOuterJoin() throws Exception {
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "JOIN T3 ON T4.b4 = T3.b3 "
+                        + "LEFT OUTER JOIN T2 ON T4.b4 = T2.b2 "
+                        + "JOIN T1 ON T4.b4 = T1.b1 "
+                        + "WHERE T4.a4 < 3";
+
+        List<String> expectedList =
+                Arrays.asList(
+                        "Hallo,Hi,Hallo,Hallo",
+                        "Hallo Welt,Hello,Hallo Welt,Hallo Welt",
+                        "Hallo Welt,Hello world,Hallo Welt,Hallo Welt");
+        assertEquals(query, expectedList);
+    }
+
+    @Test
+    public void testJoinReorderWithRightOuterJoin() throws Exception {
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4 "
+                        + "RIGHT OUTER JOIN T3 ON T4.b4 = T3.b3 "
+                        + "JOIN T2 ON T3.b3 = T2.b2 "
+                        + "JOIN T1 ON T2.b2 = T1.b1 WHERE T2.a2 <= 2";
+
+        List<String> expectedList =
+                Arrays.asList(
+                        "Hallo,Hi,Hallo,Hallo",
+                        "Hallo Welt,Hello,Hallo Welt,Hallo Welt",
+                        "Hallo Welt,Hello world,Hallo Welt,Hallo Welt");
+        assertEquals(query, expectedList);
+    }
+
+    @Test
+    public void testJoinReorderWithTrueCondition() {
+        // Only supports NestedLoopJoin.
+        if (expectedJoinType != JoinType.NestedLoopJoin()) {
+            return;
+        }
+
+        String query =
+                "SELECT T4.d4, T3.c3, T2.d2, T1.d1 FROM T4, T3, T2, T1 "
+                        + "WHERE T4.a4 <= 1 AND T3.a3 <= 1 AND T2.a2 <= 1 AND T1.a1 <= 1";
+        List<Row> results =
+                CollectionUtil.iteratorToList(
+                        DataStreamUtils.collect(
+                                tEnv.toAppendStream(tEnv.sqlQuery(query), Row.class)));
+        String expected = "+I[Hallo, Hi, Hallo, Hallo]";
+        TestBaseUtils.compareResultAsText(results, expected);
+    }
+
+    @Test
+    public void testJoinReorderWithInnerJoinAndTrueCondition() {
+        // Only supports NestedLoopJoin.
+        if (expectedJoinType != JoinType.NestedLoopJoin()) {
+            return;
+        }
+
+        String query =
+                "SELECT tab1.d4, tab1.c3, T2.d2, T1.d1 FROM T1, "
+                        + "(SELECT * FROM T3 JOIN T4 ON T4.b4 = T3.b3) tab1, T2 "
+                        + "WHERE tab1.a4 <= 1 AND tab1.a3 <= 1 AND T2.a2 <= 1 AND T1.a1 <= 1";
+        List<Row> results =
+                CollectionUtil.iteratorToList(
+                        DataStreamUtils.collect(
+                                tEnv.toAppendStream(tEnv.sqlQuery(query), Row.class)));
+        String expected = "+I[Hallo, Hi, Hallo, Hallo]";
+        TestBaseUtils.compareResultAsText(results, expected);
+    }
+
+    @Test
+    public void testJoinReorderWithMixedJoinTypeAndCondition() {
+        // Only supports NestedLoopJoin.
+        if (expectedJoinType != JoinType.NestedLoopJoin()) {
+            return;
+        }
+
+        String query =
+                "SELECT tab2.d4, tab2.c3, tab2.d2, T1.d1 FROM T1, (SELECT * FROM T4 "
+                        + "LEFT OUTER JOIN T3 ON T4.b4 = T3.b3 "
+                        + "JOIN T2 ON T4.b4 = T2.b2) tab2 "
+                        + "WHERE tab2.a4 <= 1 AND tab2.a3 <= 1 AND tab2.a2 <= 1 AND T1.a1 <= 1";
+        List<Row> results =
+                CollectionUtil.iteratorToList(
+                        DataStreamUtils.collect(
+                                tEnv.toAppendStream(tEnv.sqlQuery(query), Row.class)));
+        String expected = "+I[Hallo, Hi, Hallo, Hallo]";
+        TestBaseUtils.compareResultAsText(results, expected);
+    }
+
+    @Test
+    public void testBusyTreeJoinReorder() throws Exception {
+        CatalogColumnStatisticsDataLong longColStats =
+                new CatalogColumnStatisticsDataLong(100L, 100L, 50L, 1000L);
+        Map<String, CatalogColumnStatisticsDataBase> colStatsMap = new HashMap<>(1);
+        colStatsMap.put("b1", longColStats);
+        catalog.alterTableColumnStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T1"),
+                new CatalogColumnStatistics(colStatsMap),
+                false);
+
+        longColStats = new CatalogColumnStatisticsDataLong(100L, 100L, 500000L, 1000L);
+        colStatsMap = new HashMap<>(1);
+        colStatsMap.put("b2", longColStats);
+        catalog.alterTableColumnStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T2"),
+                new CatalogColumnStatistics(colStatsMap),
+                false);
+
+        longColStats = new CatalogColumnStatisticsDataLong(100L, 100L, 50L, 1000L);
+        colStatsMap = new HashMap<>(1);
+        colStatsMap.put("b3", longColStats);
+        catalog.alterTableColumnStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T3"),
+                new CatalogColumnStatistics(colStatsMap),
+                false);
+
+        longColStats = new CatalogColumnStatisticsDataLong(100L, 100L, 500000L, 1000L);
+        colStatsMap = new HashMap<>(1);
+        colStatsMap.put("b4", longColStats);
+        catalog.alterTableColumnStatistics(
+                new ObjectPath(tEnv.getCurrentDatabase(), "T4"),
+                new CatalogColumnStatistics(colStatsMap),
+                false);
+
+        String query =
+                "SELECT tab2.d4, tab2.c3, tab1.d2, tab1.d1 FROM "
+                        + "(SELECT * FROM T1 JOIN T2 ON T1.b1 = T2.b2) tab1 "
+                        + "JOIN (SELECT * FROM T3 JOIN T4 ON T3.b3 = T4.b4) tab2 "
+                        + "ON tab1.b2 = tab2.b4";
+
+        List<Row> results =
+                CollectionUtil.iteratorToList(
+                        DataStreamUtils.collect(
+                                tEnv.toAppendStream(tEnv.sqlQuery(query), Row.class)));
+        String expected =
+                "+I[Hallo Welt, Hello, Hallo Welt, Hallo Welt]\n"
+                        + "+I[Hallo Welt, Hello world, Hallo Welt, Hallo Welt]\n"
+                        + "+I[Hallo, Hi, Hallo, Hallo]";
+        TestBaseUtils.compareResultAsText(results, expected);
+    }
+
+    @Test
+    public void testBushyJoinReorderDpThreshold() throws Exception {

Review Comment:
   > this test is unnecessary, because it can be verified via plan test
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066524471


##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/stream/sql/join/JoinReorderITCase.java:
##########
@@ -0,0 +1,465 @@
+/*
+ * 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.runtime.stream.sql.join;
+
+import org.apache.flink.api.common.typeinfo.TypeInformation;
+import org.apache.flink.streaming.api.datastream.DataStreamUtils;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import org.apache.flink.streaming.api.functions.sink.SinkFunction;
+import org.apache.flink.table.api.Table;
+import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatistics;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataBase;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataLong;
+import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
+import org.apache.flink.table.planner.factories.TestValuesTableFactory;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinITCaseHelper;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinType;
+import org.apache.flink.table.planner.runtime.utils.TestData;
+import org.apache.flink.table.planner.runtime.utils.TestingRetractSink;
+import org.apache.flink.table.planner.utils.JavaScalaConversionUtil;
+import org.apache.flink.test.util.TestBaseUtils;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.CollectionUtil;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import scala.Enumeration;
+import scala.Tuple2;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * ITCase for JoinReorder in stream mode. This class will test {@link
+ * org.apache.flink.table.planner.plan.rules.logical.FlinkBushyJoinReorderRule} and {@link
+ * org.apache.calcite.rel.rules.LoptOptimizeJoinRule} together by changing the factor
+ * isBushyJoinReorder.
+ */
+@RunWith(Parameterized.class)
+public class JoinReorderITCase extends TestLogger {
+
+    private StreamExecutionEnvironment env;
+    private StreamTableEnvironment tEnv;
+    private Catalog catalog;
+
+    @Parameterized.Parameter(value = 0)
+    public Enumeration.Value expectedJoinType;
+
+    @Parameterized.Parameter(value = 1)
+    public boolean isBushyJoinReorder;
+
+    @Parameterized.Parameters(name = "expectedJoinType={0}, isBushyJoinReorder={1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[][] {
+                    {JoinType.BroadcastHashJoin(), false},
+                    {JoinType.BroadcastHashJoin(), true},
+                    {JoinType.HashJoin(), false},
+                    {JoinType.HashJoin(), true},
+                    {JoinType.SortMergeJoin(), false},
+                    {JoinType.SortMergeJoin(), true},
+                    {JoinType.NestedLoopJoin(), false},
+                    {JoinType.NestedLoopJoin(), true}
+                });
+    }
+
+    @Before
+    public void before() throws Exception {
+        env = StreamExecutionEnvironment.getExecutionEnvironment();
+        tEnv = StreamTableEnvironment.create(env);
+        catalog = tEnv.getCatalog(tEnv.getCurrentCatalog()).get();
+
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(ExecutionConfigOptions.TABLE_EXEC_RESOURCE_DEFAULT_PARALLELISM, 1);
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(OptimizerConfigOptions.TABLE_OPTIMIZER_JOIN_REORDER_ENABLED, true);
+
+        if (!isBushyJoinReorder) {
+            tEnv.getConfig()
+                    .getConfiguration()
+                    .set(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD, 3);
+        }

Review Comment:
   > else { tEnv.getConfig() .getConfiguration() .set(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD, 1000); }
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066558831


##########
flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/common/JoinReorderTestBase.scala:
##########
@@ -121,6 +121,8 @@ abstract class JoinReorderTestBase extends TableTestBase {
 
     util.getTableEnv.getConfig
       .set(OptimizerConfigOptions.TABLE_OPTIMIZER_JOIN_REORDER_ENABLED, Boolean.box(true))
+    util.getTableEnv.getConfig

Review Comment:
   > we should rename `JoinReorderTestBase` to LeftDeepJoinReorderTestBase
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065269586


##########
flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/logical/RewriteMultiJoinConditionRule.scala:
##########
@@ -45,7 +48,12 @@ class RewriteMultiJoinConditionRule
     // currently only supports all join types are INNER join
     val isAllInnerJoin = multiJoin.getJoinTypes.forall(_ eq JoinRelType.INNER)
     val (equiJoinFilters, _) = partitionJoinFilters(multiJoin)
-    !multiJoin.isFullOuterJoin && isAllInnerJoin && equiJoinFilters.size > 1
+    val numJoinInputs = multiJoin.getInputs.size()
+    val dpThreshold = ShortcutUtils
+      .unwrapContext(multiJoin)
+      .getTableConfig
+      .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD)
+    (numJoinInputs > dpThreshold) && !multiJoin.isFullOuterJoin && isAllInnerJoin && equiJoinFilters.size > 1

Review Comment:
   For bushy join reorder, this is no need to rewrite multi join condition to avoid increasing the search space of the bushy join reorder algorithm. Like `A J B J C`, `A and B` have join condition,  `B and C` have join condition. `RewriteMultiJoinConditionRule` will add a new condition `A and C`: this is useful for Lopt join rule, but for bushy join rule,  the condition `A and B` and  `B and C` can get two three-tables join reorder result( `(A J B) J C` and `(B J C) J A`, and only one `A J B J C` result will be retained). Btw, I will add comment for why will do it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] godfreyhe closed pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
godfreyhe closed pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm
URL: https://github.com/apache/flink/pull/21530


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink busy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1058019236


##########
docs/layouts/shortcodes/generated/optimizer_config_configuration.html:
##########
@@ -89,5 +89,17 @@
             <td>Boolean</td>
             <td>When it is true, the optimizer will collect and use the statistics from source connectors if the source extends from SupportsStatisticReport and the statistics from catalog is UNKNOWN.Default value is true.</td>
         </tr>
+        <tr>
+            <td><h5>table.optimizer.busy-join-reorder</h5><br> <span class="label label-primary">Batch</span> <span class="label label-primary">Streaming</span></td>

Review Comment:
   > This option can be removed, since we can use `table.optimizer.busy-join-reorder-dp-threshold` <= 0 to disable busy-join-reorder
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065280135


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(

Review Comment:
   > nit: reorderInnerJoin
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065379525


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our bushy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(mq, foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                joinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(
+            RelMetadataQuery mq, Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan, mq)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    relBuilder, oneSidePlan, otherSidePlan, multiJoin, isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlan == null
+                                || newJoinPlan.get().betterThan(existingPlan, mq)) {
+                            nextLevel.put(newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.factorIds);
+        resSet.retainAll(otherSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.factorIds, otherSidePlan.factorIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.factorIds.size() >= otherSidePlan.factorIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newFactorIds.addAll(leftPlan.factorIds);
+        newFactorIds.addAll(rightPlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.factorIds),
+                        new ArrayList<>(rightPlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(leftPlan.relNode)
+                                .push(rightPlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        return Optional.of(new JoinPlan(newFactorIds, newJoin));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftFactorIds,
+            List<Integer> rightFactorIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBushyJoin(
+                                        rexBuilder, multiJoin, leftFactorIds, rightFactorIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneFactorIds,
+            Set<Integer> otherFactorIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneFactorIds.size() + otherFactorIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneFactorNumbers = 0;
+                int otherFactorNumbers = 0;
+                for (int oneFactorId : oneFactorIds) {
+                    if (factorsRefByJoinFilter.get(oneFactorId)) {
+                        oneFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneFactorId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherFactorId : otherFactorIds) {
+                    if (factorsRefByJoinFilter.get(otherFactorId)) {
+                        otherFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherFactorId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneFactorNumbers > 0
+                        && otherFactorNumbers > 0
+                        && oneFactorNumbers + otherFactorNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> factorIds;
+        final RelNode relNode;
+
+        JoinPlan(LinkedHashSet<Integer> factorIds, RelNode relNode) {
+            this.factorIds = factorIds;
+            this.relNode = relNode;
+        }
+
+        private boolean betterThan(JoinPlan otherPlan, RelMetadataQuery mq) {
+            RelOptCost thisCost = mq.getCumulativeCost(this.relNode);
+            RelOptCost otherCost = mq.getCumulativeCost(otherPlan.relNode);
+            if (thisCost == null
+                    || otherCost == null
+                    || thisCost.getRows() == 0.0
+                    || otherCost.getRows() == 0.0) {
+                return false;
+            } else {
+                return thisCost.isLt(otherCost);
+            }
+        }
+    }
+
+    private static class RexInputConverterForBushyJoin extends RexShuttle {
+        private final RexBuilder rexBuilder;
+        private final LoptMultiJoin multiJoin;
+        private final List<Integer> leftFactorIds;
+        private final List<Integer> rightFactorIds;
+
+        public RexInputConverterForBushyJoin(
+                RexBuilder rexBuilder,
+                LoptMultiJoin multiJoin,
+                List<Integer> leftFactorIds,
+                List<Integer> rightFactorIds) {
+            this.rexBuilder = rexBuilder;

Review Comment:
   > we can get RexBuilder from RelNode
   
   There is no input relNode for this class. We can only get it from multiJoin. But, I think it's better to set `rexBuilder` as a n input of the constructor.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on PR #21530:
URL: https://github.com/apache/flink/pull/21530#issuecomment-1377219432

   @flinkbot run azure


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066506235


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);

Review Comment:
   > can we add some check about the remaining joins are cross join
   > 
   > nit: these code can be extract as a method, which can be used in `addToTopForOuterJoin`
   
   I think there is no need and no way to check it .
   Why no need? : If a factor can be added to `MultiJoin`, it have only three situation: 1) inner join; 2) left/right/full outer join; 3. cross join. So, if the factor is not inner join and outer join, it must be cross join.
   
   Why no way? : `MultiJoin` will not store the join condition of cross join.
   
   WDYT?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066523167


##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/stream/sql/join/JoinReorderITCase.java:
##########
@@ -0,0 +1,465 @@
+/*
+ * 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.runtime.stream.sql.join;
+
+import org.apache.flink.api.common.typeinfo.TypeInformation;
+import org.apache.flink.streaming.api.datastream.DataStreamUtils;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import org.apache.flink.streaming.api.functions.sink.SinkFunction;
+import org.apache.flink.table.api.Table;
+import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatistics;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataBase;
+import org.apache.flink.table.catalog.stats.CatalogColumnStatisticsDataLong;
+import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
+import org.apache.flink.table.planner.factories.TestValuesTableFactory;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinITCaseHelper;
+import org.apache.flink.table.planner.runtime.batch.sql.join.JoinType;
+import org.apache.flink.table.planner.runtime.utils.TestData;
+import org.apache.flink.table.planner.runtime.utils.TestingRetractSink;
+import org.apache.flink.table.planner.utils.JavaScalaConversionUtil;
+import org.apache.flink.test.util.TestBaseUtils;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.CollectionUtil;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import scala.Enumeration;
+import scala.Tuple2;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * ITCase for JoinReorder in stream mode. This class will test {@link
+ * org.apache.flink.table.planner.plan.rules.logical.FlinkBushyJoinReorderRule} and {@link
+ * org.apache.calcite.rel.rules.LoptOptimizeJoinRule} together by changing the factor
+ * isBushyJoinReorder.
+ */
+@RunWith(Parameterized.class)
+public class JoinReorderITCase extends TestLogger {
+
+    private StreamExecutionEnvironment env;
+    private StreamTableEnvironment tEnv;
+    private Catalog catalog;
+
+    @Parameterized.Parameter(value = 0)
+    public Enumeration.Value expectedJoinType;
+
+    @Parameterized.Parameter(value = 1)
+    public boolean isBushyJoinReorder;
+
+    @Parameterized.Parameters(name = "expectedJoinType={0}, isBushyJoinReorder={1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[][] {
+                    {JoinType.BroadcastHashJoin(), false},
+                    {JoinType.BroadcastHashJoin(), true},
+                    {JoinType.HashJoin(), false},
+                    {JoinType.HashJoin(), true},
+                    {JoinType.SortMergeJoin(), false},
+                    {JoinType.SortMergeJoin(), true},
+                    {JoinType.NestedLoopJoin(), false},
+                    {JoinType.NestedLoopJoin(), true}

Review Comment:
   > there are no broadcast join, hash join, sort merge join, and nested loop join for streaming
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066491418


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    /**
+     * Creates the topmost projection that will sit on top of the selected join ordering. The
+     * projection needs to match the original join ordering. Also, places any post-join filters on
+     * top of the project.
+     */
+    private static RelNode creatTopProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.clear();
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    /** Found possible join plans for the next level based on the found plans in the prev levels. */
+    private static Map<Set<Integer>, JoinPlan> foundNextLevel(
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> foundPlans,
+            LoptMultiJoin multiJoin) {
+        Map<Set<Integer>, JoinPlan> currentLevelJoinPlanMap = new LinkedHashMap<>();
+        int foundPlansLevel = foundPlans.size() - 1;
+        int joinLeftSideLevel = 0;
+        int joinRightSideLevel = foundPlansLevel;
+        while (joinLeftSideLevel <= joinRightSideLevel) {
+            List<JoinPlan> joinLeftSidePlans =
+                    new ArrayList<>(foundPlans.get(joinLeftSideLevel).values());
+            int planSize = joinLeftSidePlans.size();
+            for (int i = 0; i < planSize; i++) {
+                JoinPlan joinLeftSidePlan = joinLeftSidePlans.get(i);
+                List<JoinPlan> joinRightSidePlans;
+                if (joinLeftSideLevel == joinRightSideLevel) {
+                    // If left side level number equals right side level number. We can remove those
+                    // top 'i' plans which already judged in right side plans to decrease search
+                    // spaces.
+                    joinRightSidePlans = new ArrayList<>(joinLeftSidePlans);
+                    if (i > 0) {
+                        joinRightSidePlans.subList(0, i).clear();
+                    }
+                } else {
+                    joinRightSidePlans =
+                            new ArrayList<>(foundPlans.get(joinRightSideLevel).values());
+                }
+                for (JoinPlan joinRightSidePlan : joinRightSidePlans) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildInnerJoin(
+                                    relBuilder, joinLeftSidePlan, joinRightSidePlan, multiJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlanInCurrentLevel =
+                                currentLevelJoinPlanMap.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlanInCurrentLevel == null
+                                || newJoinPlan.get().betterThan(existingPlanInCurrentLevel)) {
+                            currentLevelJoinPlanMap.put(
+                                    newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            joinLeftSideLevel += 1;
+            joinRightSideLevel = foundPlansLevel - joinLeftSideLevel;
+        }
+        return currentLevelJoinPlanMap;
+    }
+
+    private static Optional<JoinPlan> buildInnerJoin(
+            RelBuilder relBuilder,
+            JoinPlan leftSidePlan,
+            JoinPlan rightSidePlan,
+            LoptMultiJoin multiJoin) {
+        // Intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(leftSidePlan.factorIds);
+        resSet.retainAll(rightSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                getConditionsAndJoinType(
+                        leftSidePlan.factorIds, rightSidePlan.factorIds, multiJoin, false);
+        if (!joinConditions.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConditions.get().f0;
+        JoinRelType joinType = joinConditions.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan newLeftSidePlan;
+        JoinPlan newRightSidePlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (leftSidePlan.factorIds.size() >= rightSidePlan.factorIds.size()) {
+            newLeftSidePlan = leftSidePlan;
+            newRightSidePlan = rightSidePlan;
+        } else {
+            newLeftSidePlan = rightSidePlan;
+            newRightSidePlan = leftSidePlan;
+        }
+        newFactorIds.addAll(newLeftSidePlan.factorIds);
+        newFactorIds.addAll(newRightSidePlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(newLeftSidePlan.factorIds),
+                        new ArrayList<>(newRightSidePlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        relBuilder.clear();
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(newLeftSidePlan.relNode)
+                                .push(newRightSidePlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        return Optional.of(new JoinPlan(newFactorIds, newJoin));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftFactorIds,
+            List<Integer> rightFactorIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBushyJoin(
+                                        rexBuilder, multiJoin, leftFactorIds, rightFactorIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneFactorIds,
+            Set<Integer> otherFactorIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneFactorIds.size() + otherFactorIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneFactorNumbers = 0;
+                int otherFactorNumbers = 0;
+                for (int oneFactorId : oneFactorIds) {
+                    if (factorsRefByJoinFilter.get(oneFactorId)) {
+                        oneFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneFactorId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherFactorId : otherFactorIds) {
+                    if (factorsRefByJoinFilter.get(otherFactorId)) {
+                        otherFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherFactorId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneFactorNumbers > 0
+                        && otherFactorNumbers > 0
+                        && oneFactorNumbers + otherFactorNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> factorIds;
+        final RelNode relNode;
+
+        JoinPlan(LinkedHashSet<Integer> factorIds, RelNode relNode) {
+            this.factorIds = factorIds;
+            this.relNode = relNode;
+        }
+
+        private boolean betterThan(JoinPlan otherPlan) {
+            RelMetadataQuery mq = this.relNode.getCluster().getMetadataQuery();
+            RelOptCost thisCost = mq.getCumulativeCost(this.relNode);
+            RelOptCost otherCost = mq.getCumulativeCost(otherPlan.relNode);
+            if (thisCost == null || otherCost == null) {
+                return false;
+            } else {
+                return thisCost.isLt(otherCost);
+            }
+        }
+    }
+
+    /**
+     * This class is used to convert the rexInputRef index of the join condition before building a
+     * new join.
+     */
+    private static class RexInputConverterForBushyJoin extends RexShuttle {
+        private final RexBuilder rexBuilder;
+        private final LoptMultiJoin multiJoin;
+        private final List<Integer> leftFactorIds;
+        private final List<Integer> rightFactorIds;
+
+        public RexInputConverterForBushyJoin(
+                RexBuilder rexBuilder,
+                LoptMultiJoin multiJoin,
+                List<Integer> leftFactorIds,
+                List<Integer> rightFactorIds) {
+            this.rexBuilder = rexBuilder;
+            this.multiJoin = multiJoin;
+            this.leftFactorIds = leftFactorIds;
+            this.rightFactorIds = rightFactorIds;
+        }
+
+        @Override
+        public RexNode visitInputRef(RexInputRef var) {
+            int index = var.getIndex();
+            int currentIndex = 0;
+            int factorRef = multiJoin.findRef(index);
+            if (leftFactorIds.contains(factorRef)) {
+                for (Integer leftFactorId : leftFactorIds) {
+                    if (leftFactorId == factorRef) {
+                        currentIndex += findFactorIndex(index, multiJoin);
+                        return new RexInputRef(currentIndex, var.getType());
+                    } else {
+                        currentIndex += multiJoin.getNumFieldsInJoinFactor(leftFactorId);
+                    }
+                }
+            } else {
+                for (int leftFactor : leftFactorIds) {
+                    currentIndex += multiJoin.getNumFieldsInJoinFactor(leftFactor);
+                }
+                for (Integer rightFactorId : rightFactorIds) {
+                    if (rightFactorId == factorRef) {
+                        currentIndex += findFactorIndex(index, multiJoin);
+                        return rexBuilder.makeInputRef(var.getType(), currentIndex);

Review Comment:
   > return new RexInputRef(currentIndex, var.getType());
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066499085


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)

Review Comment:
   > Why is it always `JoinRelType.LEFT` here ?
   
   As a simple case for example. `((A RJ B) IJ C)`, the left side  join condition of `(A RJ B) IJ C` comes from `B`.  This case can be converted to `MultiJoin` whose join type in `MultiJoin` is `[Right, Inner, Inner]`. So, In our bushyJoinReoderRule, we will first reorder `B` and `C` to form `B IJ C`. Then, we will add `A` to the top, and it join type need to be converted to left join. So, for each left/right join, if it can be added into a `MultiJoin`, it will be converted to left join.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066493761


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);

Review Comment:
   > it's more easy to debug that assign the result of `getBestPlan(lastLevelOfInnerJoin)` to a variable
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066506352


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);

Review Comment:
   
   
   > nit: these code can be extract as a method, which can be used in `addToTopForOuterJoin`
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065276259


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkJoinReorderRule.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.planner.utils.ShortcutUtils;
+
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+
+/**
+ * Flink join reorder rule, which can change the join order of input relNode tree.
+ *
+ * <p>It is triggered by the ({@link MultiJoin}).
+ *
+ * <p>In this rule, there are two specific join reorder strategies can be chosen, one is {@link
+ * LoptOptimizeJoinRule}, another is {@link FlinkBushyJoinReorderRule}. Which rule is selected
+ * depends on the parameter {@link
+ * OptimizerConfigOptions#TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD}.
+ */
+public class FlinkJoinReorderRule extends RelRule<FlinkJoinReorderRule.Config>
+        implements TransformationRule {
+
+    public static final FlinkJoinReorderRule INSTANCE =
+            FlinkJoinReorderRule.Config.DEFAULT.toRule();
+
+    private static final LoptOptimizeJoinRule LOPT_JOIN_REORDER =
+            LoptOptimizeJoinRule.Config.DEFAULT.toRule();
+
+    private static final FlinkBushyJoinReorderRule BUSHY_JOIN_REORDER =
+            FlinkBushyJoinReorderRule.Config.DEFAULT.toRule();
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkJoinReorderRule(FlinkJoinReorderRule.Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(
+                FlinkJoinReorderRule.Config.DEFAULT
+                        .withRelBuilderFactory(relBuilderFactory)
+                        .as(FlinkJoinReorderRule.Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final MultiJoin multiJoinRel = call.rel(0);
+        int numJoinInputs = multiJoinRel.getInputs().size();
+        int dpThreshold =

Review Comment:
   > nit: rename to bushyTreeThreshold ?
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065269586


##########
flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/logical/RewriteMultiJoinConditionRule.scala:
##########
@@ -45,7 +48,12 @@ class RewriteMultiJoinConditionRule
     // currently only supports all join types are INNER join
     val isAllInnerJoin = multiJoin.getJoinTypes.forall(_ eq JoinRelType.INNER)
     val (equiJoinFilters, _) = partitionJoinFilters(multiJoin)
-    !multiJoin.isFullOuterJoin && isAllInnerJoin && equiJoinFilters.size > 1
+    val numJoinInputs = multiJoin.getInputs.size()
+    val dpThreshold = ShortcutUtils
+      .unwrapContext(multiJoin)
+      .getTableConfig
+      .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSHY_JOIN_REORDER_THRESHOLD)
+    (numJoinInputs > dpThreshold) && !multiJoin.isFullOuterJoin && isAllInnerJoin && equiJoinFilters.size > 1

Review Comment:
   For bushy join reorder, this is no need to rewrite multi join condition while it will increase the search base of the bushy join reorder algorithm, but it will not bring more possible join pairs for bushy join. Like `A J B J C`, `A and B` have join condition,  `B and C` have join condition. `RewriteMultiJoinConditionRule` will add a new condition `A and C`: this is useful for Lopt join rule, but for bushy join rule,  the condition `A and B` and  `B and C` can get all three-tables join reorder result( `(A J B) J C` and `(A J C) J B` and `(A J B) J C`  ). Btw, I will add comment for why will do it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] godfreyhe commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
godfreyhe commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065837801


##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/batch/sql/join/JoinReorderITCase.java:
##########
@@ -0,0 +1,276 @@
+/*
+ * 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.runtime.batch.sql.join;
+
+import org.apache.flink.table.api.TableEnvironment;
+import org.apache.flink.table.api.config.ExecutionConfigOptions;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.ObjectPath;
+import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
+import org.apache.flink.table.planner.factories.TestValuesTableFactory;
+import org.apache.flink.table.planner.runtime.utils.BatchTestBase;
+import org.apache.flink.table.planner.runtime.utils.TestData;
+import org.apache.flink.table.planner.utils.JavaScalaConversionUtil;
+import org.apache.flink.types.Row;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import scala.Enumeration;
+
+/** ITCase for JoinReorder in batch mode. */
+@RunWith(Parameterized.class)
+public class JoinReorderITCase extends BatchTestBase {
+
+    private TableEnvironment tEnv;
+    private Catalog catalog;
+
+    @Parameterized.Parameter(value = 0)
+    public Enumeration.Value expectedJoinType;
+
+    @Parameterized.Parameter(value = 1)
+    public boolean isBushyJoinReorder;
+
+    @Parameterized.Parameters(name = "expectedJoinType={0}, isBushyJoinReorder={1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[][] {
+                    {JoinType.BroadcastHashJoin(), false},
+                    {JoinType.BroadcastHashJoin(), true},
+                    {JoinType.HashJoin(), false},
+                    {JoinType.HashJoin(), true},
+                    {JoinType.SortMergeJoin(), false},
+                    {JoinType.SortMergeJoin(), true},
+                    {JoinType.NestedLoopJoin(), false},
+                    {JoinType.NestedLoopJoin(), true}
+                });
+    }
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+        tEnv = tEnv();
+        catalog = tEnv.getCatalog(tEnv.getCurrentCatalog()).get();
+
+        tEnv.getConfig()
+                .getConfiguration()
+                .set(ExecutionConfigOptions.TABLE_EXEC_RESOURCE_DEFAULT_PARALLELISM, 1);

Review Comment:
   There is no relationship between them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065709913


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,620 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>

Review Comment:
   > add some comment at the core path to help reading
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1065710198


##########
flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRuleTest.xml:
##########
@@ -0,0 +1,198 @@
+<?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>

Review Comment:
   > it seems that all optimized plan are not bushy tree
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink bushy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1066490734


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBushyJoinReorderRule.java:
##########
@@ -0,0 +1,634 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink bushy join reorder rule, which will convert {@link MultiJoin} to a bushy join tree.
+ *
+ * <p>In this bushy join reorder strategy, the join reorder step is as follows:
+ *
+ * <p>First step, we will reorder all the inner join type inputs in the multiJoin. We adopt the
+ * concept of level in dynamic programming, and the latter layer will use the results stored in the
+ * previous levels. First, we put all input factor (each input factor in {@link MultiJoin}) into
+ * level 0, then we build all two-inputs join at level 1 based on the {@link FlinkCost} of level 0,
+ * then we will build three-inputs join based on the previous two levels, then four-inputs joins ...
+ * etc, util we reorder all the inner join type input factors in the multiJoin. When building
+ * m-inputs join, we only keep the best plan (have the lowest {@link FlinkCost}) for the same set of
+ * m inputs. E.g., for three-inputs join, we keep only the best plan for inputs {A, B, C} among
+ * plans (A J B) J C, (A J C) J B, (B J C) J A.
+ *
+ * <p>Second step, we will add all outer join factors to the top of reordered join tree generated by
+ * the first step. E.g., for the example (((A LJ B) IJ C) IJ D). we will first reorder A, C and D
+ * using the first step strategy, get ((A IJ B) IJ C). Then, we will add D to the top, get (((A IJ
+ * B) IJ C) LJ D).
+ *
+ * <p>Third step, if there are factors whose join condition is true, we will add these factors to
+ * the top in the final step.
+ */
+public class FlinkBushyJoinReorderRule extends RelRule<FlinkBushyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    /** Creates a FlinkBushyJoinReorderRule. */
+    protected FlinkBushyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBushyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        RelNode bestOrder = findBestOrder(relBuilder, multiJoin);
+        call.transformTo(bestOrder);
+    }
+
+    /**
+     * Find best join reorder using bushy join reorder strategy. We will first try to reorder all
+     * the inner join type input factors in the multiJoin. Then, we will add all outer join factors
+     * to the top of reordered join tree generated by the first step. If there are factors, which
+     * join condition is true, we will add these factors to the top in the final step.
+     */
+    private static RelNode findBestOrder(RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // Reorder all the inner join type input factors in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlansForInnerJoin =
+                reorderInnerJoin(relBuilder, multiJoin);
+
+        Map<Set<Integer>, JoinPlan> lastLevelOfInnerJoin =
+                foundPlansForInnerJoin.get(foundPlansForInnerJoin.size() - 1);
+
+        JoinPlan containOuterJoinPlan;
+        // Add all outer join factors in the multiJoin (including left/right/full) on the
+        // top of tree if outer join condition exists in multiJoin.
+        if (outerJoinConditionExists(multiJoin)) {
+            containOuterJoinPlan =
+                    addToTopForOuterJoin(getBestPlan(lastLevelOfInnerJoin), multiJoin, relBuilder);
+        } else {
+            containOuterJoinPlan = getBestPlan(lastLevelOfInnerJoin);
+        }
+
+        JoinPlan finalPlan;
+        // Add these factors whose join condition is true to the top.
+        if (containOuterJoinPlan.factorIds.size() != multiJoin.getNumJoinFactors()) {
+            finalPlan = addToTopForTrueCondition(containOuterJoinPlan, multiJoin, relBuilder);
+        } else {
+            finalPlan = containOuterJoinPlan;
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatTopProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reorderInnerJoin(
+            RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put all join factors in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> firstLevelJoinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < numJoinFactors; i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                firstLevelJoinPlanMap.put(set1, new JoinPlan(set2, joinFactor));
+            }
+        }
+        foundPlans.add(firstLevelJoinPlanMap);
+
+        // Build plans for next levels until the found plans size equals the number of join factors,
+        // or no possible plan exists for next level.
+        while (foundPlans.size() < numJoinFactors) {
+            Map<Set<Integer>, JoinPlan> nextLevelJoinPlanMap =
+                    foundNextLevel(relBuilder, new ArrayList<>(foundPlans), multiJoin);
+            if (nextLevelJoinPlanMap.size() == 0) {
+                break;
+            }
+            foundPlans.add(nextLevelJoinPlanMap);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean outerJoinConditionExists(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().betterThan(bestPlan)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // Make new join condition and get new join type.
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                    getConditionsAndJoinType(
+                            bestPlan.factorIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConditions.isPresent()) {
+                continue;
+            } else {
+                // Is left/right outer join, but we all given left join type.
+                Set<RexCall> conditions = joinConditions.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    private static JoinPlan addToTopForTrueCondition(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.factorIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.factorIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode);
+    }
+
+    /**
+     * Creates the topmost projection that will sit on top of the selected join ordering. The
+     * projection needs to match the original join ordering. Also, places any post-join filters on
+     * top of the project.
+     */
+    private static RelNode creatTopProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.factorIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.clear();
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    /** Found possible join plans for the next level based on the found plans in the prev levels. */
+    private static Map<Set<Integer>, JoinPlan> foundNextLevel(
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> foundPlans,
+            LoptMultiJoin multiJoin) {
+        Map<Set<Integer>, JoinPlan> currentLevelJoinPlanMap = new LinkedHashMap<>();
+        int foundPlansLevel = foundPlans.size() - 1;
+        int joinLeftSideLevel = 0;
+        int joinRightSideLevel = foundPlansLevel;
+        while (joinLeftSideLevel <= joinRightSideLevel) {
+            List<JoinPlan> joinLeftSidePlans =
+                    new ArrayList<>(foundPlans.get(joinLeftSideLevel).values());
+            int planSize = joinLeftSidePlans.size();
+            for (int i = 0; i < planSize; i++) {
+                JoinPlan joinLeftSidePlan = joinLeftSidePlans.get(i);
+                List<JoinPlan> joinRightSidePlans;
+                if (joinLeftSideLevel == joinRightSideLevel) {
+                    // If left side level number equals right side level number. We can remove those
+                    // top 'i' plans which already judged in right side plans to decrease search
+                    // spaces.
+                    joinRightSidePlans = new ArrayList<>(joinLeftSidePlans);
+                    if (i > 0) {
+                        joinRightSidePlans.subList(0, i).clear();
+                    }
+                } else {
+                    joinRightSidePlans =
+                            new ArrayList<>(foundPlans.get(joinRightSideLevel).values());
+                }
+                for (JoinPlan joinRightSidePlan : joinRightSidePlans) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildInnerJoin(
+                                    relBuilder, joinLeftSidePlan, joinRightSidePlan, multiJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlanInCurrentLevel =
+                                currentLevelJoinPlanMap.get(newJoinPlan.get().factorIds);
+                        // check if it's the first plan for the factor set, or it's a better plan
+                        // than the existing one due to lower cost.
+                        if (existingPlanInCurrentLevel == null
+                                || newJoinPlan.get().betterThan(existingPlanInCurrentLevel)) {
+                            currentLevelJoinPlanMap.put(
+                                    newJoinPlan.get().factorIds, newJoinPlan.get());
+                        }
+                    }
+                }
+            }
+            joinLeftSideLevel += 1;
+            joinRightSideLevel = foundPlansLevel - joinLeftSideLevel;
+        }
+        return currentLevelJoinPlanMap;
+    }
+
+    private static Optional<JoinPlan> buildInnerJoin(
+            RelBuilder relBuilder,
+            JoinPlan leftSidePlan,
+            JoinPlan rightSidePlan,
+            LoptMultiJoin multiJoin) {
+        // Intersect, should not join two overlapping factor sets.
+        Set<Integer> resSet = new HashSet<>(leftSidePlan.factorIds);
+        resSet.retainAll(rightSidePlan.factorIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConditions =
+                getConditionsAndJoinType(
+                        leftSidePlan.factorIds, rightSidePlan.factorIds, multiJoin, false);
+        if (!joinConditions.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConditions.get().f0;
+        JoinRelType joinType = joinConditions.get().f1;
+
+        LinkedHashSet<Integer> newFactorIds = new LinkedHashSet<>();
+        JoinPlan newLeftSidePlan;
+        JoinPlan newRightSidePlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (leftSidePlan.factorIds.size() >= rightSidePlan.factorIds.size()) {
+            newLeftSidePlan = leftSidePlan;
+            newRightSidePlan = rightSidePlan;
+        } else {
+            newLeftSidePlan = rightSidePlan;
+            newRightSidePlan = leftSidePlan;
+        }
+        newFactorIds.addAll(newLeftSidePlan.factorIds);
+        newFactorIds.addAll(newRightSidePlan.factorIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(newLeftSidePlan.factorIds),
+                        new ArrayList<>(newRightSidePlan.factorIds),
+                        rexCalls,
+                        multiJoin);
+
+        relBuilder.clear();
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(newLeftSidePlan.relNode)
+                                .push(newRightSidePlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        return Optional.of(new JoinPlan(newFactorIds, newJoin));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftFactorIds,
+            List<Integer> rightFactorIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBushyJoin(
+                                        rexBuilder, multiJoin, leftFactorIds, rightFactorIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneFactorIds,
+            Set<Integer> otherFactorIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneFactorIds.size() + otherFactorIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneFactorNumbers = 0;
+                int otherFactorNumbers = 0;
+                for (int oneFactorId : oneFactorIds) {
+                    if (factorsRefByJoinFilter.get(oneFactorId)) {
+                        oneFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneFactorId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherFactorId : otherFactorIds) {
+                    if (factorsRefByJoinFilter.get(otherFactorId)) {
+                        otherFactorNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherFactorId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneFactorNumbers > 0
+                        && otherFactorNumbers > 0
+                        && oneFactorNumbers + otherFactorNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> factorIds;
+        final RelNode relNode;
+
+        JoinPlan(LinkedHashSet<Integer> factorIds, RelNode relNode) {
+            this.factorIds = factorIds;
+            this.relNode = relNode;
+        }
+
+        private boolean betterThan(JoinPlan otherPlan) {
+            RelMetadataQuery mq = this.relNode.getCluster().getMetadataQuery();
+            RelOptCost thisCost = mq.getCumulativeCost(this.relNode);
+            RelOptCost otherCost = mq.getCumulativeCost(otherPlan.relNode);
+            if (thisCost == null || otherCost == null) {
+                return false;
+            } else {
+                return thisCost.isLt(otherCost);
+            }
+        }
+    }
+
+    /**
+     * This class is used to convert the rexInputRef index of the join condition before building a
+     * new join.
+     */
+    private static class RexInputConverterForBushyJoin extends RexShuttle {
+        private final RexBuilder rexBuilder;
+        private final LoptMultiJoin multiJoin;
+        private final List<Integer> leftFactorIds;
+        private final List<Integer> rightFactorIds;
+
+        public RexInputConverterForBushyJoin(

Review Comment:
   > JoinCondititionShuffle
   
   Done!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [flink] swuferhong commented on a diff in pull request #21530: [FLINK-30376][table-planner] Introduce a new flink busy join reorder rule which based on greedy algorithm

Posted by GitBox <gi...@apache.org>.
swuferhong commented on code in PR #21530:
URL: https://github.com/apache/flink/pull/21530#discussion_r1058027477


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkBusyJoinReorderRule.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * 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.rules.logical;
+
+import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.table.api.config.OptimizerConfigOptions;
+import org.apache.flink.table.planner.plan.cost.FlinkCost;
+import org.apache.flink.table.planner.plan.utils.FlinkRelOptUtil;
+import org.apache.flink.table.planner.utils.ShortcutUtils;
+
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.LoptMultiJoin;
+import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
+import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Flink busy join reorder rule, which will convert {@link MultiJoin} to a busy join tree.
+ *
+ * <p>In this busy join reorder strategy, we will first try to reorder all the inner join type
+ * inputs in the multiJoin, and then add all outer join type inputs on the top.
+ *
+ * <p>First, reordering all the inner join type inputs in the multiJoin. We adopt the concept of
+ * level in dynamic programming, and the latter layer will use the results stored in the previous
+ * layers. First, we put all inputs (each input in {@link MultiJoin}) into level 0, then we build
+ * all two-inputs join at level 1 based on the {@link FlinkCost} of level 0, then we will build
+ * three-inputs join based on the previous two levels, then four-inputs joins ... etc, util we
+ * reorder all the inner join type inputs in the multiJoin. When building m-inputs join, we only
+ * keep the best plan (have the lowest {@link FlinkCost}) for the same set of m inputs. E.g., for
+ * three-inputs join, we keep only the best plan for inputs {A, B, C} among plans (A J B) J C, (A J
+ * C) J B, (B J C) J A.
+ *
+ * <p>Second, we will add all outer join type inputs in the MultiJoin on the top.
+ */
+public class FlinkBusyJoinReorderRule extends RelRule<FlinkBusyJoinReorderRule.Config>
+        implements TransformationRule {
+
+    public static final LoptOptimizeJoinRule MULTI_JOIN_OPTIMIZE =
+            LoptOptimizeJoinRule.Config.DEFAULT.toRule();
+
+    /** Creates an SparkJoinReorderRule. */
+    protected FlinkBusyJoinReorderRule(Config config) {
+        super(config);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(RelBuilderFactory relBuilderFactory) {
+        this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory).as(Config.class));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public FlinkBusyJoinReorderRule(
+            RelFactories.JoinFactory joinFactory,
+            RelFactories.ProjectFactory projectFactory,
+            RelFactories.FilterFactory filterFactory) {
+        this(RelBuilder.proto(joinFactory, projectFactory, filterFactory));
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        final RelBuilder relBuilder = call.builder();
+        final MultiJoin multiJoinRel = call.rel(0);
+        Boolean enableBusyJoin =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER);
+        Integer dpThreshold =
+                ShortcutUtils.unwrapContext(multiJoinRel)
+                        .getTableConfig()
+                        .get(OptimizerConfigOptions.TABLE_OPTIMIZER_BUSY_JOIN_REORDER_DP_THRESHOLD);
+        final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+        int numJoinFactors = multiJoin.getNumJoinFactors();
+
+        if (enableBusyJoin && numJoinFactors <= dpThreshold) {
+            RelNode bestOrder = findBestOrder(call.getMetadataQuery(), relBuilder, multiJoin);
+            call.transformTo(bestOrder);
+        } else {
+            MULTI_JOIN_OPTIMIZE.onMatch(call);
+        }
+    }
+
+    private static RelNode findBestOrder(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        // In our busy join reorder strategy, we will first try to reorder all the inner join type
+        // inputs in the multiJoin, and then add all outer join type inputs on the top.
+        // First, reorder all the inner join type inputs in the multiJoin.
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = reOrderInnerJoin(mq, relBuilder, multiJoin);
+
+        JoinPlan finalPlan;
+        // Second, add all outer join type inputs in the multiJoin on the top.
+        if (canOuterJoin(multiJoin)) {
+            finalPlan =
+                    addToTopForOuterJoin(
+                            getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                            multiJoin,
+                            relBuilder);
+        } else {
+            if (foundPlans.size() != multiJoin.getNumJoinFactors()) {
+                finalPlan =
+                        addToTop(
+                                getBestPlan(foundPlans.get(foundPlans.size() - 1)),
+                                multiJoin,
+                                relBuilder);
+            } else {
+                assert foundPlans.get(foundPlans.size() - 1).size() == 1;
+                finalPlan = new ArrayList<>(foundPlans.get(foundPlans.size() - 1).values()).get(0);
+            }
+        }
+
+        final List<String> fieldNames = multiJoin.getMultiJoinRel().getRowType().getFieldNames();
+        return creatToProject(relBuilder, multiJoin, finalPlan, fieldNames);
+    }
+
+    private static List<Map<Set<Integer>, JoinPlan>> reOrderInnerJoin(
+            RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin) {
+        List<Map<Set<Integer>, JoinPlan>> foundPlans = new ArrayList<>();
+
+        // First, we put each input in MultiJoin into level 0.
+        Map<Set<Integer>, JoinPlan> joinPlanMap = new LinkedHashMap<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!multiJoin.isNullGenerating(i)) {
+                HashSet<Integer> set1 = new HashSet<>();
+                LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
+                set1.add(i);
+                set2.add(i);
+                RelNode joinFactor = multiJoin.getJoinFactor(i);
+                RelOptCost cost = mq.getCumulativeCost(joinFactor);
+                joinPlanMap.put(
+                        set1,
+                        new JoinPlan(
+                                set2,
+                                joinFactor,
+                                new FlinkCost(
+                                        cost.getRows(), cost.getCpu(), cost.getIo(), 0.0, 0.0)));
+            }
+        }
+        foundPlans.add(joinPlanMap);
+
+        // Build plans for next levels until the last level has only one plan. This plan contains
+        // all inputs that can be joined, so there's no need to continue
+        while (foundPlans.size() < multiJoin.getNumJoinFactors()) {
+            Map<Set<Integer>, JoinPlan> levelPlan =
+                    searchLevel(mq, relBuilder, new ArrayList<>(foundPlans), multiJoin, false);
+            if (levelPlan.size() == 0) {
+                break;
+            }
+            foundPlans.add(levelPlan);
+        }
+
+        return foundPlans;
+    }
+
+    private static boolean canOuterJoin(LoptMultiJoin multiJoin) {
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (multiJoin.getOuterJoinCond(i) != null
+                    && RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)).size() != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static JoinPlan getBestPlan(Map<Set<Integer>, JoinPlan> levelPlan) {
+        JoinPlan bestPlan = null;
+        for (Map.Entry<Set<Integer>, JoinPlan> entry : levelPlan.entrySet()) {
+            if (bestPlan == null || entry.getValue().cost.isLt(bestPlan.cost)) {
+                bestPlan = entry.getValue();
+            }
+        }
+
+        return bestPlan;
+    }
+
+    private static JoinPlan addToTopForOuterJoin(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+
+            // make new join condition
+            Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                    getConditionsAndJoinType(
+                            bestPlan.itemIds, Collections.singleton(index), multiJoin, true);
+
+            if (!joinConds.isPresent()) {
+                // join type is always left.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, rexBuilder.makeLiteral(true))
+                                .build();
+            } else {
+                Set<RexCall> conditions = joinConds.get().f0;
+                List<RexNode> rexCalls = new ArrayList<>(conditions);
+                Set<RexCall> newCondition =
+                        convertToNewCondition(
+                                new ArrayList<>(set),
+                                Collections.singletonList(index),
+                                rexCalls,
+                                multiJoin);
+                // all given left join.
+                leftNode =
+                        relBuilder
+                                .push(leftNode)
+                                .push(rightNode)
+                                .join(JoinRelType.LEFT, newCondition)
+                                .build();
+            }
+            set.add(index);
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static JoinPlan addToTop(
+            JoinPlan bestPlan, LoptMultiJoin multiJoin, RelBuilder relBuilder) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        List<Integer> remainIndexes = new ArrayList<>();
+        for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+            if (!bestPlan.itemIds.contains(i)) {
+                remainIndexes.add(i);
+            }
+        }
+
+        RelNode leftNode = bestPlan.relNode;
+        LinkedHashSet<Integer> set = new LinkedHashSet<>(bestPlan.itemIds);
+        for (int index : remainIndexes) {
+            set.add(index);
+            RelNode rightNode = multiJoin.getJoinFactor(index);
+            leftNode =
+                    relBuilder
+                            .push(leftNode)
+                            .push(rightNode)
+                            .join(
+                                    multiJoin.getMultiJoinRel().getJoinTypes().get(index),
+                                    rexBuilder.makeLiteral(true))
+                            .build();
+        }
+        return new JoinPlan(set, leftNode, new FlinkCost(0.0, 0.0, 0.0, 0.0, 0.0));
+    }
+
+    private static RelNode creatToProject(
+            RelBuilder relBuilder,
+            LoptMultiJoin multiJoin,
+            JoinPlan finalPlan,
+            List<String> fieldNames) {
+        List<RexNode> newProjExprs = new ArrayList<>();
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+
+        List<Integer> newJoinOrder = new ArrayList<>(finalPlan.itemIds);
+        int nJoinFactors = multiJoin.getNumJoinFactors();
+        List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
+
+        // create a mapping from each factor to its field offset in the join
+        // ordering
+        final Map<Integer, Integer> factorToOffsetMap = new HashMap<>();
+        for (int pos = 0, fieldStart = 0; pos < nJoinFactors; pos++) {
+            factorToOffsetMap.put(newJoinOrder.get(pos), fieldStart);
+            fieldStart += multiJoin.getNumFieldsInJoinFactor(newJoinOrder.get(pos));
+        }
+
+        for (int currFactor = 0; currFactor < nJoinFactors; currFactor++) {
+            // if the factor is the right factor in a removable self-join,
+            // then where possible, remap references to the right factor to
+            // the corresponding reference in the left factor
+            Integer leftFactor = null;
+            if (multiJoin.isRightFactorInRemovableSelfJoin(currFactor)) {
+                leftFactor = multiJoin.getOtherSelfJoinFactor(currFactor);
+            }
+            for (int fieldPos = 0;
+                    fieldPos < multiJoin.getNumFieldsInJoinFactor(currFactor);
+                    fieldPos++) {
+                int newOffset =
+                        requireNonNull(
+                                        factorToOffsetMap.get(currFactor),
+                                        () -> "factorToOffsetMap.get(currFactor)")
+                                + fieldPos;
+                if (leftFactor != null) {
+                    Integer leftOffset = multiJoin.getRightColumnMapping(currFactor, fieldPos);
+                    if (leftOffset != null) {
+                        newOffset =
+                                requireNonNull(
+                                                factorToOffsetMap.get(leftFactor),
+                                                "factorToOffsetMap.get(leftFactor)")
+                                        + leftOffset;
+                    }
+                }
+                newProjExprs.add(
+                        rexBuilder.makeInputRef(
+                                fields.get(newProjExprs.size()).getType(), newOffset));
+            }
+        }
+
+        relBuilder.push(finalPlan.relNode);
+        relBuilder.project(newProjExprs, fieldNames);
+
+        // Place the post-join filter (if it exists) on top of the final
+        // projection.
+        RexNode postJoinFilter = multiJoin.getMultiJoinRel().getPostJoinFilter();
+        if (postJoinFilter != null) {
+            relBuilder.filter(postJoinFilter);
+        }
+        return relBuilder.build();
+    }
+
+    private static Map<Set<Integer>, JoinPlan> searchLevel(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            List<Map<Set<Integer>, JoinPlan>> existingLevels,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        Map<Set<Integer>, List<JoinPlan>> printNextLevel = new LinkedHashMap<>();
+        Map<Set<Integer>, JoinPlan> nextLevel = new LinkedHashMap<>();
+        int k = 0;
+        int lev = existingLevels.size() - 1;
+        while (k <= lev - k) {
+            ArrayList<JoinPlan> oneSideCandidates = new ArrayList<>(existingLevels.get(k).values());
+            int oneSideSize = oneSideCandidates.size();
+            for (int i = 0; i < oneSideSize; i++) {
+                JoinPlan oneSidePlan = oneSideCandidates.get(i);
+                ArrayList<JoinPlan> otherSideCandidates;
+                if (k == lev - k) {
+                    otherSideCandidates = new ArrayList<>(oneSideCandidates);
+                    if (i > 0) {
+                        otherSideCandidates.subList(0, i).clear();
+                    }
+                } else {
+                    otherSideCandidates = new ArrayList<>(existingLevels.get(lev - k).values());
+                }
+                for (JoinPlan otherSidePlan : otherSideCandidates) {
+                    Optional<JoinPlan> newJoinPlan =
+                            buildJoin(
+                                    mq,
+                                    relBuilder,
+                                    oneSidePlan,
+                                    otherSidePlan,
+                                    multiJoin,
+                                    isOuterJoin);
+                    if (newJoinPlan.isPresent()) {
+                        JoinPlan existingPlan = nextLevel.get(newJoinPlan.get().itemIds);
+                        // check if it's the first plan for the item set, or it's a better plan than
+                        // the existing one due to lower cost.
+                        if (existingPlan == null || newJoinPlan.get().betterThan(existingPlan)) {
+                            nextLevel.put(newJoinPlan.get().itemIds, newJoinPlan.get());
+                        }
+
+                        if (printNextLevel.get(newJoinPlan.get().itemIds) == null) {
+                            printNextLevel.put(
+                                    newJoinPlan.get().itemIds,
+                                    Collections.singletonList(newJoinPlan.get()));
+                        } else {
+                            List<JoinPlan> joinPlans =
+                                    new ArrayList<>(printNextLevel.get(newJoinPlan.get().itemIds));
+                            joinPlans.add(newJoinPlan.get());
+                            printNextLevel.put(newJoinPlan.get().itemIds, joinPlans);
+                        }
+                    }
+                }
+            }
+            k += 1;
+        }
+
+        // print
+        for (Map.Entry<Set<Integer>, List<JoinPlan>> entry : printNextLevel.entrySet()) {
+            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++");
+            System.out.printf("item sets: %s%n", entry.getKey());
+            for (JoinPlan joinPlan : entry.getValue()) {
+                System.out.println("--------------------------------------------");
+                System.out.printf("costs: %s%n", joinPlan.cost.getRows());
+                System.out.println(
+                        FlinkRelOptUtil.toString(
+                                joinPlan.relNode,
+                                SqlExplainLevel.ALL_ATTRIBUTES,
+                                false,
+                                false,
+                                false,
+                                false,
+                                false));
+                System.out.println("--------------------------------------------");
+            }
+            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++");
+        }
+        return nextLevel;
+    }
+
+    private static Optional<JoinPlan> buildJoin(
+            RelMetadataQuery mq,
+            RelBuilder relBuilder,
+            JoinPlan oneSidePlan,
+            JoinPlan otherSidePlan,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        // intersect, should not join two overlapping item sets.
+        Set<Integer> resSet = new HashSet<>(oneSidePlan.itemIds);
+        resSet.retainAll(otherSidePlan.itemIds);
+        if (!resSet.isEmpty()) {
+            return Optional.empty();
+        }
+
+        Optional<Tuple2<Set<RexCall>, JoinRelType>> joinConds =
+                getConditionsAndJoinType(
+                        oneSidePlan.itemIds, otherSidePlan.itemIds, multiJoin, isOuterJoin);
+        if (!joinConds.isPresent()) {
+            return Optional.empty();
+        }
+
+        Set<RexCall> conditions = joinConds.get().f0;
+        JoinRelType joinType = joinConds.get().f1;
+
+        LinkedHashSet<Integer> newItemIds = new LinkedHashSet<>();
+        JoinPlan leftPlan;
+        JoinPlan rightPlan;
+        // put the deeper side on the left, tend to build a left-deep tree.
+        if (oneSidePlan.itemIds.size() >= otherSidePlan.itemIds.size()) {
+            leftPlan = oneSidePlan;
+            rightPlan = otherSidePlan;
+        } else {
+            leftPlan = otherSidePlan;
+            rightPlan = oneSidePlan;
+            if (isOuterJoin) {
+                joinType = (joinType == JoinRelType.LEFT) ? JoinRelType.RIGHT : JoinRelType.LEFT;
+            }
+        }
+        newItemIds.addAll(leftPlan.itemIds);
+        newItemIds.addAll(rightPlan.itemIds);
+
+        List<RexNode> rexCalls = new ArrayList<>(conditions);
+        Set<RexCall> newCondition =
+                convertToNewCondition(
+                        new ArrayList<>(leftPlan.itemIds),
+                        new ArrayList<>(rightPlan.itemIds),
+                        rexCalls,
+                        multiJoin);
+
+        Join newJoin =
+                (Join)
+                        relBuilder
+                                .push(leftPlan.relNode)
+                                .push(rightPlan.relNode)
+                                .join(joinType, newCondition)
+                                .build();
+
+        RelOptCost cost = mq.getCumulativeCost(newJoin);
+        return Optional.of(
+                new JoinPlan(
+                        newItemIds,
+                        newJoin,
+                        new FlinkCost(cost.getRows(), cost.getCpu(), cost.getIo(), 0.0, 0.0)));
+    }
+
+    private static Set<RexCall> convertToNewCondition(
+            List<Integer> leftItemIds,
+            List<Integer> rightItemIds,
+            List<RexNode> rexNodes,
+            LoptMultiJoin multiJoin) {
+        RexBuilder rexBuilder = multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
+        Set<RexCall> newCondition = new HashSet<>();
+        for (RexNode cond : rexNodes) {
+            RexCall rexCond = (RexCall) cond;
+            List<RexNode> resultRexNode = new ArrayList<>();
+            for (RexNode rexNode : rexCond.getOperands()) {
+                rexNode =
+                        rexNode.accept(
+                                new RexInputConverterForBusyJoin(
+                                        rexBuilder, multiJoin, leftItemIds, rightItemIds));
+                resultRexNode.add(rexNode);
+            }
+            RexNode resultRex = rexBuilder.makeCall(rexCond.op, resultRexNode);
+            newCondition.add((RexCall) resultRex);
+        }
+
+        return newCondition;
+    }
+
+    private static Optional<Tuple2<Set<RexCall>, JoinRelType>> getConditionsAndJoinType(
+            Set<Integer> oneItemIds,
+            Set<Integer> otherItemIds,
+            LoptMultiJoin multiJoin,
+            boolean isOuterJoin) {
+        if (oneItemIds.size() + otherItemIds.size() < 2) {
+            return Optional.empty();
+        }
+        JoinRelType joinType = JoinRelType.INNER;
+        if (multiJoin.getMultiJoinRel().isFullOuterJoin()) {
+            assert multiJoin.getNumJoinFactors() == 2;
+            joinType = JoinRelType.FULL;
+        }
+
+        Set<RexCall> resultRexCall = new HashSet<>();
+        List<RexNode> joinConditions = new ArrayList<>();
+        if (isOuterJoin) {
+            for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+                joinConditions.addAll(RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(i)));
+            }
+        } else {
+            joinConditions = multiJoin.getJoinFilters();
+        }
+
+        for (RexNode joinCond : joinConditions) {
+            if (joinCond instanceof RexCall) {
+                RexCall callCondition = (RexCall) joinCond;
+                ImmutableBitSet factorsRefByJoinFilter =
+                        multiJoin.getFactorsRefByJoinFilter(callCondition);
+                int oneItemNumbers = 0;
+                int otherItemNumbers = 0;
+                for (int oneItemId : oneItemIds) {
+                    if (factorsRefByJoinFilter.get(oneItemId)) {
+                        oneItemNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(oneItemId)) {
+                            joinType = JoinRelType.RIGHT;
+                        }
+                    }
+                }
+                for (int otherItemId : otherItemIds) {
+                    if (factorsRefByJoinFilter.get(otherItemId)) {
+                        otherItemNumbers++;
+                        if (isOuterJoin && multiJoin.isNullGenerating(otherItemId)) {
+                            joinType = JoinRelType.LEFT;
+                        }
+                    }
+                }
+
+                if (oneItemNumbers > 0
+                        && otherItemNumbers > 0
+                        && oneItemNumbers + otherItemNumbers
+                                == factorsRefByJoinFilter.asSet().size()) {
+                    resultRexCall.add(callCondition);
+                }
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        if (resultRexCall.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(Tuple2.of(resultRexCall, joinType));
+        }
+    }
+
+    // ~ Inner Classes ----------------------------------------------------------
+    private static class JoinPlan {
+        final LinkedHashSet<Integer> itemIds;

Review Comment:
   > What does `itemId` mean ?
   
   In this version, I change item -> factor, factor means one multi join input. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org