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>