You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by dw...@apache.org on 2025/06/09 14:44:53 UTC

(flink) branch master updated: [FLINK-37814][table-planner] Add FlinkRightJoinToLeftJoinRule (#26627)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 58d3cb4dee7 [FLINK-37814][table-planner] Add FlinkRightJoinToLeftJoinRule (#26627)
58d3cb4dee7 is described below

commit 58d3cb4dee7317202afdffe158b4d109f4328b09
Author: Stepan Stepanishchev <11...@users.noreply.github.com>
AuthorDate: Mon Jun 9 21:44:46 2025 +0700

    [FLINK-37814][table-planner] Add FlinkRightJoinToLeftJoinRule (#26627)
---
 .../logical/FlinkRightJoinToLeftJoinRule.java      | 135 ++++++++++++
 .../logical/FlinkRightJoinToLeftJoinRuleTest.java  | 133 ++++++++++++
 .../logical/FlinkRightJoinToLeftJoinRuleTest.xml   | 226 +++++++++++++++++++++
 3 files changed, 494 insertions(+)

diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkRightJoinToLeftJoinRule.java b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkRightJoinToLeftJoinRule.java
new file mode 100644
index 00000000000..682673791d0
--- /dev/null
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/FlinkRightJoinToLeftJoinRule.java
@@ -0,0 +1,135 @@
+/*
+ * 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.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.logical.LogicalJoin;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.tools.RelBuilder;
+import org.immutables.value.Value;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Rule to convert RIGHT JOIN to LEFT JOIN since Multi-Way Join operator works only with LEFT joins.
+ */
+@Value.Enclosing
+public class FlinkRightJoinToLeftJoinRule extends RelRule<FlinkRightJoinToLeftJoinRule.Config>
+        implements TransformationRule {
+
+    public static final FlinkRightJoinToLeftJoinRule INSTANCE =
+            FlinkRightJoinToLeftJoinRule.Config.DEFAULT.toRule();
+
+    /** Creates a FlinkRightJoinToLeftJoinRule. */
+    public FlinkRightJoinToLeftJoinRule(FlinkRightJoinToLeftJoinRule.Config config) {
+        super(config);
+    }
+
+    @Override
+    public boolean matches(RelOptRuleCall call) {
+        Join origJoin = call.rel(0);
+        return origJoin.getJoinType() == JoinRelType.RIGHT;
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+        Join origJoin = call.rel(0);
+        RelNode left = call.rel(1);
+        RelNode right = call.rel(2);
+
+        RexNode newCondition = shiftCondition(origJoin, left, right);
+
+        Join leftJoin =
+                origJoin.copy(
+                        origJoin.getTraitSet(), newCondition, right, left, JoinRelType.LEFT, false);
+
+        RelBuilder relBuilder = call.builder();
+        relBuilder.push(leftJoin);
+
+        RelNode project = reorderProjectedFields(left, right, relBuilder);
+
+        call.transformTo(project);
+    }
+
+    private RelNode reorderProjectedFields(RelNode left, RelNode right, RelBuilder relBuilder) {
+        int nFieldsOnLeft = left.getRowType().getFieldList().size();
+        int nFieldsOnRight = right.getRowType().getFieldList().size();
+        List<RexNode> reorderedFields = new ArrayList<>();
+
+        for (int i = 0; i < nFieldsOnLeft; i++) {
+            reorderedFields.add(relBuilder.field(i + nFieldsOnRight));
+        }
+
+        for (int i = 0; i < nFieldsOnRight; i++) {
+            reorderedFields.add(relBuilder.field(i));
+        }
+
+        return relBuilder.project(reorderedFields).build();
+    }
+
+    private RexNode shiftCondition(Join joinRel, RelNode left, RelNode right) {
+        RexNode condition = joinRel.getCondition();
+        int nFieldsOnLeft = left.getRowType().getFieldList().size();
+        int nFieldsOnRight = right.getRowType().getFieldList().size();
+        int[] adjustments = new int[nFieldsOnLeft + nFieldsOnRight];
+        for (int i = 0; i < nFieldsOnLeft + nFieldsOnRight; i++) {
+            adjustments[i] = i < nFieldsOnLeft ? nFieldsOnRight : -nFieldsOnLeft;
+        }
+
+        return condition.accept(
+                new RelOptUtil.RexInputConverter(
+                        joinRel.getCluster().getRexBuilder(),
+                        joinRel.getRowType().getFieldList(),
+                        adjustments));
+    }
+
+    /** Rule configuration. */
+    @Value.Immutable(singleton = false)
+    public interface Config extends RelRule.Config {
+        FlinkRightJoinToLeftJoinRule.Config DEFAULT =
+                ImmutableFlinkRightJoinToLeftJoinRule.Config.builder()
+                        .build()
+                        .as(FlinkRightJoinToLeftJoinRule.Config.class)
+                        .withOperandFor(LogicalJoin.class);
+
+        @Override
+        default FlinkRightJoinToLeftJoinRule toRule() {
+            return new FlinkRightJoinToLeftJoinRule(this);
+        }
+
+        /** Defines an operand tree for the given classes. */
+        default FlinkRightJoinToLeftJoinRule.Config withOperandFor(
+                Class<? extends Join> joinClass) {
+            return withOperandSupplier(
+                            b0 ->
+                                    b0.operand(joinClass)
+                                            .inputs(
+                                                    b1 -> b1.operand(RelNode.class).anyInputs(),
+                                                    b2 -> b2.operand(RelNode.class).anyInputs()))
+                    .as(FlinkRightJoinToLeftJoinRule.Config.class);
+        }
+    }
+}
diff --git a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/rules/logical/FlinkRightJoinToLeftJoinRuleTest.java b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/rules/logical/FlinkRightJoinToLeftJoinRuleTest.java
new file mode 100644
index 00000000000..8ae0f833637
--- /dev/null
+++ b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/rules/logical/FlinkRightJoinToLeftJoinRuleTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.DataTypes;
+import org.apache.flink.table.api.Schema;
+import org.apache.flink.table.api.TableConfig;
+import org.apache.flink.table.planner.plan.optimize.program.FlinkChainedProgram;
+import org.apache.flink.table.planner.plan.optimize.program.FlinkGroupProgramBuilder;
+import org.apache.flink.table.planner.plan.optimize.program.FlinkHepRuleSetProgramBuilder;
+import org.apache.flink.table.planner.plan.optimize.program.FlinkStreamProgram;
+import org.apache.flink.table.planner.plan.optimize.program.HEP_RULES_EXECUTION_TYPE;
+import org.apache.flink.table.planner.plan.optimize.program.StreamOptimizeContext;
+import org.apache.flink.table.planner.utils.StreamTableTestUtil;
+import org.apache.flink.table.planner.utils.TableConfigUtils;
+import org.apache.flink.table.planner.utils.TableTestBase;
+
+import org.apache.calcite.plan.hep.HepMatchOrder;
+import org.apache.calcite.tools.RuleSets;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Tests for {@link FlinkRightJoinToLeftJoinRule}. */
+public class FlinkRightJoinToLeftJoinRuleTest extends TableTestBase {
+
+    private StreamTableTestUtil util;
+
+    @BeforeEach
+    public void setup() {
+        util = streamTestUtil(TableConfig.getDefault());
+
+        util.buildStreamProgram(FlinkStreamProgram.DEFAULT_REWRITE());
+
+        TableConfig config = util.tableEnv().getConfig();
+
+        FlinkChainedProgram<StreamOptimizeContext> streamProgram =
+                TableConfigUtils.getCalciteConfig(config).getStreamProgram().get();
+
+        streamProgram.addLast(
+                "right-to-left-rules",
+                FlinkGroupProgramBuilder.<StreamOptimizeContext>newBuilder()
+                        .addProgram(
+                                FlinkHepRuleSetProgramBuilder.<StreamOptimizeContext>newBuilder()
+                                        .setHepRulesExecutionType(
+                                                HEP_RULES_EXECUTION_TYPE.RULE_SEQUENCE())
+                                        .setHepMatchOrder(HepMatchOrder.BOTTOM_UP)
+                                        .add(RuleSets.ofList(FlinkRightJoinToLeftJoinRule.INSTANCE))
+                                        .build(),
+                                "right-join-to-left-join")
+                        .build());
+
+        util.addTableSource(
+                "T1",
+                Schema.newBuilder()
+                        .column("a", DataTypes.INT())
+                        .column("b", DataTypes.BIGINT())
+                        .build());
+
+        util.addTableSource(
+                "T2",
+                Schema.newBuilder()
+                        .column("c", DataTypes.INT())
+                        .column("d", DataTypes.BIGINT())
+                        .column("e", DataTypes.BIGINT())
+                        .build());
+
+        util.addTableSource(
+                "T3",
+                Schema.newBuilder()
+                        .column("f", DataTypes.INT())
+                        .column("g", DataTypes.BIGINT())
+                        .build());
+    }
+
+    @Test
+    public void testRightJoin() {
+        util.verifyRelPlan("SELECT * FROM T1 RIGHT JOIN T2 ON a = c");
+    }
+
+    @Test
+    public void testNestedProject() {
+        util.verifyRelPlan(
+                "SELECT * FROM (SELECT * FROM T1 JOIN T2 ON a = c) RIGHT JOIN T3 ON a = f");
+    }
+
+    @Test
+    public void testRightInnerJoinChain() {
+        util.verifyRelPlan("SELECT * FROM T2 RIGHT JOIN T3 on T2.c = T3.f JOIN T1 ON T1.a = T2.c");
+    }
+
+    @Test
+    public void testRightRightJoinChain() {
+        util.verifyRelPlan(
+                "SELECT * FROM T2 RIGHT JOIN T3 on T2.c = T3.f RIGHT JOIN T1 ON T1.a = T2.c");
+    }
+
+    @Test
+    public void testRightLeftJoinChain() {
+        util.verifyRelPlan(
+                "SELECT * FROM T2 RIGHT JOIN T3 on T2.c = T3.f LEFT JOIN T1 ON T1.a = T2.c");
+    }
+
+    @Test
+    public void testLeftJoinRemainsUnchanged() {
+        util.verifyRelPlan("SELECT * FROM T1 LEFT JOIN T2 ON a = c");
+    }
+
+    @Test
+    public void testRightJoinWithExpressionCondition() {
+        util.verifyRelPlan("SELECT * FROM T1 RIGHT JOIN T2 ON a + 1 = c - 1");
+    }
+
+    @Test
+    public void testRightJoinWithProjection() {
+        util.verifyRelPlan("SELECT b, d FROM T1 RIGHT JOIN T2 ON a = c");
+    }
+}
diff --git a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/rules/logical/FlinkRightJoinToLeftJoinRuleTest.xml b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/rules/logical/FlinkRightJoinToLeftJoinRuleTest.xml
new file mode 100644
index 00000000000..246e2bcf274
--- /dev/null
+++ b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/rules/logical/FlinkRightJoinToLeftJoinRuleTest.xml
@@ -0,0 +1,226 @@
+<?xml version="1.0" ?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to you under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<Root>
+	<TestCase name="testRightJoin">
+		<Resource name="sql">
+			<![CDATA[SELECT * FROM T1 RIGHT JOIN T2 ON a = c]]>
+		</Resource>
+		<Resource name="ast">
+			<![CDATA[
+LogicalProject(a=[$0], b=[$1], c=[$2], d=[$3], e=[$4])
++- LogicalJoin(condition=[=($0, $2)], joinType=[right])
+   :- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+   +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+]]>
+		</Resource>
+		<Resource name="optimized rel plan">
+			<![CDATA[
+LogicalProject(a=[$0], b=[$1], c=[$2], d=[$3], e=[$4])
++- LogicalProject(a=[$3], b=[$4], c=[$0], d=[$1], e=[$2])
+   +- LogicalJoin(condition=[=($3, $0)], joinType=[left])
+      :- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+]]>
+		</Resource>
+	</TestCase>
+
+	<TestCase name="testNestedProject">
+		<Resource name="sql">
+			<![CDATA[SELECT * FROM (SELECT * FROM T1 JOIN T2 ON a = c) RIGHT JOIN T3 ON a = f]]>
+		</Resource>
+		<Resource name="ast">
+			<![CDATA[
+LogicalProject(a=[$0], b=[$1], c=[$2], d=[$3], e=[$4], f=[$5], g=[$6])
++- LogicalJoin(condition=[=($0, $5)], joinType=[right])
+   :- LogicalProject(a=[$0], b=[$1], c=[$2], d=[$3], e=[$4])
+   :  +- LogicalJoin(condition=[=($0, $2)], joinType=[inner])
+   :     :- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+   :     +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+   +- LogicalTableScan(table=[[default_catalog, default_database, T3]])
+]]>
+		</Resource>
+		<Resource name="optimized rel plan">
+			<![CDATA[
+LogicalProject(a=[$0], b=[$1], c=[$2], d=[$3], e=[$4], f=[$5], g=[$6])
++- LogicalProject(a=[$2], b=[$3], c=[$4], d=[$5], e=[$6], f=[$0], g=[$1])
+   +- LogicalJoin(condition=[=($2, $0)], joinType=[left])
+      :- LogicalTableScan(table=[[default_catalog, default_database, T3]])
+      +- LogicalProject(a=[$0], b=[$1], c=[$2], d=[$3], e=[$4])
+         +- LogicalJoin(condition=[=($0, $2)], joinType=[inner])
+            :- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+            +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+]]>
+		</Resource>
+	</TestCase>
+
+	<TestCase name="testRightInnerJoinChain">
+		<Resource name="sql">
+			<![CDATA[SELECT * FROM T2 RIGHT JOIN T3 on T2.c = T3.f JOIN T1 ON T1.a = T2.c]]>
+		</Resource>
+		<Resource name="ast">
+			<![CDATA[
+LogicalProject(c=[$0], d=[$1], e=[$2], f=[$3], g=[$4], a=[$5], b=[$6])
++- LogicalJoin(condition=[=($5, $0)], joinType=[inner])
+   :- LogicalJoin(condition=[=($0, $3)], joinType=[right])
+   :  :- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+   :  +- LogicalTableScan(table=[[default_catalog, default_database, T3]])
+   +- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+]]>
+		</Resource>
+		<Resource name="optimized rel plan">
+			<![CDATA[
+LogicalProject(c=[$0], d=[$1], e=[$2], f=[$3], g=[$4], a=[$5], b=[$6])
++- LogicalJoin(condition=[=($5, $0)], joinType=[inner])
+   :- LogicalProject(c=[$2], d=[$3], e=[$4], f=[$0], g=[$1])
+   :  +- LogicalJoin(condition=[=($2, $0)], joinType=[left])
+   :     :- LogicalTableScan(table=[[default_catalog, default_database, T3]])
+   :     +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+   +- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+]]>
+		</Resource>
+	</TestCase>
+
+	<TestCase name="testRightRightJoinChain">
+		<Resource name="sql">
+			<![CDATA[SELECT * FROM T2 RIGHT JOIN T3 on T2.c = T3.f RIGHT JOIN T1 ON T1.a = T2.c]]>
+		</Resource>
+		<Resource name="ast">
+			<![CDATA[
+LogicalProject(c=[$0], d=[$1], e=[$2], f=[$3], g=[$4], a=[$5], b=[$6])
++- LogicalJoin(condition=[=($5, $0)], joinType=[right])
+   :- LogicalJoin(condition=[=($0, $3)], joinType=[right])
+   :  :- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+   :  +- LogicalTableScan(table=[[default_catalog, default_database, T3]])
+   +- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+]]>
+		</Resource>
+		<Resource name="optimized rel plan">
+			<![CDATA[
+LogicalProject(c=[$0], d=[$1], e=[$2], f=[$3], g=[$4], a=[$5], b=[$6])
++- LogicalProject(c=[$2], d=[$3], e=[$4], f=[$5], g=[$6], a=[$0], b=[$1])
+   +- LogicalJoin(condition=[=($0, $2)], joinType=[left])
+      :- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+      +- LogicalProject(c=[$2], d=[$3], e=[$4], f=[$0], g=[$1])
+         +- LogicalJoin(condition=[=($2, $0)], joinType=[left])
+            :- LogicalTableScan(table=[[default_catalog, default_database, T3]])
+            +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+]]>
+		</Resource>
+	</TestCase>
+
+	<TestCase name="testRightLeftJoinChain">
+		<Resource name="sql">
+			<![CDATA[SELECT * FROM T2 RIGHT JOIN T3 on T2.c = T3.f LEFT JOIN T1 ON T1.a = T2.c]]>
+		</Resource>
+		<Resource name="ast">
+			<![CDATA[
+LogicalProject(c=[$0], d=[$1], e=[$2], f=[$3], g=[$4], a=[$5], b=[$6])
++- LogicalJoin(condition=[=($5, $0)], joinType=[left])
+   :- LogicalJoin(condition=[=($0, $3)], joinType=[right])
+   :  :- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+   :  +- LogicalTableScan(table=[[default_catalog, default_database, T3]])
+   +- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+]]>
+		</Resource>
+		<Resource name="optimized rel plan">
+			<![CDATA[
+LogicalProject(c=[$0], d=[$1], e=[$2], f=[$3], g=[$4], a=[$5], b=[$6])
++- LogicalJoin(condition=[=($5, $0)], joinType=[left])
+   :- LogicalProject(c=[$2], d=[$3], e=[$4], f=[$0], g=[$1])
+   :  +- LogicalJoin(condition=[=($2, $0)], joinType=[left])
+   :     :- LogicalTableScan(table=[[default_catalog, default_database, T3]])
+   :     +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+   +- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+]]>
+		</Resource>
+	</TestCase>
+
+	<TestCase name="testLeftJoinRemainsUnchanged">
+		<Resource name="sql">
+			<![CDATA[SELECT * FROM T1 LEFT JOIN T2 ON a = c]]>
+		</Resource>
+		<Resource name="ast">
+			<![CDATA[
+LogicalProject(a=[$0], b=[$1], c=[$2], d=[$3], e=[$4])
++- LogicalJoin(condition=[=($0, $2)], joinType=[left])
+   :- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+   +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+]]>
+		</Resource>
+		<Resource name="optimized rel plan">
+			<![CDATA[
+LogicalProject(a=[$0], b=[$1], c=[$2], d=[$3], e=[$4])
++- LogicalJoin(condition=[=($0, $2)], joinType=[left])
+   :- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+   +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+]]>
+		</Resource>
+	</TestCase>
+
+	<TestCase name="testRightJoinWithExpressionCondition">
+		<Resource name="sql">
+			<![CDATA[SELECT * FROM T1 RIGHT JOIN T2 ON a + 1 = c - 1]]>
+		</Resource>
+		<Resource name="ast">
+			<![CDATA[
+LogicalProject(a=[$0], b=[$1], c=[$2], d=[$3], e=[$4])
++- LogicalProject(a=[$0], b=[$1], c=[$3], d=[$4], e=[$5])
+   +- LogicalJoin(condition=[=($2, $6)], joinType=[right])
+      :- LogicalProject(a=[$0], b=[$1], $f2=[+($0, 1)])
+      :  +- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+      +- LogicalProject(c=[$0], d=[$1], e=[$2], $f3=[-($0, 1)])
+         +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+]]>
+		</Resource>
+		<Resource name="optimized rel plan">
+			<![CDATA[
+LogicalProject(a=[$0], b=[$1], c=[$2], d=[$3], e=[$4])
++- LogicalProject(a=[$0], b=[$1], c=[$3], d=[$4], e=[$5])
+   +- LogicalProject(a=[$4], b=[$5], $f2=[$6], c=[$0], d=[$1], e=[$2], $f3=[$3])
+      +- LogicalJoin(condition=[=($6, $3)], joinType=[left])
+         :- LogicalProject(c=[$0], d=[$1], e=[$2], $f3=[-($0, 1)])
+         :  +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+         +- LogicalProject(a=[$0], b=[$1], $f2=[+($0, 1)])
+            +- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+]]>
+		</Resource>
+	</TestCase>
+
+	<TestCase name="testRightJoinWithProjection">
+		<Resource name="sql">
+			<![CDATA[SELECT b, d FROM T1 RIGHT JOIN T2 ON a = c]]>
+		</Resource>
+		<Resource name="ast">
+			<![CDATA[
+LogicalProject(b=[$1], d=[$3])
++- LogicalJoin(condition=[=($0, $2)], joinType=[right])
+   :- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+   +- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+]]>
+		</Resource>
+		<Resource name="optimized rel plan">
+			<![CDATA[
+LogicalProject(b=[$1], d=[$3])
++- LogicalProject(a=[$3], b=[$4], c=[$0], d=[$1], e=[$2])
+   +- LogicalJoin(condition=[=($3, $0)], joinType=[left])
+      :- LogicalTableScan(table=[[default_catalog, default_database, T2]])
+      +- LogicalTableScan(table=[[default_catalog, default_database, T1]])
+]]>
+		</Resource>
+	</TestCase>
+</Root>