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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2572ae88314 Support outer join expression bind (#35019)
2572ae88314 is described below

commit 2572ae88314edd694f1c3e2741ab279ae443c2a6
Author: ZhangCheng <chengzh...@apache.org>
AuthorDate: Mon Mar 17 13:44:50 2025 +0800

    Support outer join expression bind (#35019)
    
    * Support outer join expression bind
    
    * Support outer join expression bind
---
 RELEASE-NOTES.md                                   |  1 +
 .../dml/expression/ExpressionSegmentBinder.java    |  5 ++
 .../expression/type/OuterJoinExpressionBinder.java | 52 +++++++++++++++++
 .../type/OuterJoinExpressionBinderTest.java        | 66 ++++++++++++++++++++++
 .../sql/binder/dialect/oracle/OracleBinderIT.java  | 25 ++++++++
 .../binder/src/test/resources/cases/dml/select.xml | 62 ++++++++++++++++++++
 .../binder/src/test/resources/sqls/dml/select.xml  |  1 +
 7 files changed, 212 insertions(+)

diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index bfc2c27561e..6a1428145a5 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -20,6 +20,7 @@
 1. SQL Binder: Support select aggregation function sql bind in projection and 
having - [#34379](https://github.com/apache/shardingsphere/pull/34379)
 1. SQL Binder: Support column definition for the WITH clause and 
ExternalTableBinderContext in 
CommonTableExpressionBinder.[#34384](https://github.com/apache/shardingsphere/pull/34384)
 1. SQL Binder: Support case when then else segment bind - 
[#34600](https://github.com/apache/shardingsphere/pull/34600)
+1. SQL Binder: Support outer join expression bind - 
[#35019](https://github.com/apache/shardingsphere/pull/35019)
 1. SQL Parser: Support MySQL SELECT CAST AS YEAR statement parse - 
[#34638](https://github.com/apache/shardingsphere/pull/34638)
 1. SQL Parser: Support MySQL SELECT MAX(ALL expr) statement parse - 
[#34639](https://github.com/apache/shardingsphere/pull/34639)
 1. SQL Parser: Support MySQL INSERT with GEOMCOLLECTION function parse - 
[#34654](https://github.com/apache/shardingsphere/pull/34654)
diff --git 
a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/ExpressionSegmentBinder.java
 
b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/ExpressionSegmentBinder.java
index f19b438d092..1a47b2ba240 100644
--- 
a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/ExpressionSegmentBinder.java
+++ 
b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/ExpressionSegmentBinder.java
@@ -33,6 +33,7 @@ import 
org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type
 import 
org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type.FunctionExpressionSegmentBinder;
 import 
org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type.InExpressionBinder;
 import 
org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type.NotExpressionBinder;
+import 
org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type.OuterJoinExpressionBinder;
 import 
org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type.SubquerySegmentBinder;
 import 
org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.TableSegmentBinderContext;
 import 
org.apache.shardingsphere.infra.binder.engine.statement.SQLStatementBinderContext;
@@ -48,6 +49,7 @@ import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.NotE
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.subquery.SubqueryExpressionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.AggregationDistinctProjectionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.AggregationProjectionSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.join.OuterJoinExpression;
 
 /**
  * Expression segment binder.
@@ -104,6 +106,9 @@ public final class ExpressionSegmentBinder {
         if (segment instanceof CaseWhenExpression) {
             return CaseWhenExpressionBinder.bind((CaseWhenExpression) segment, 
parentSegmentType, binderContext, tableBinderContexts, 
outerTableBinderContexts);
         }
+        if (segment instanceof OuterJoinExpression) {
+            return OuterJoinExpressionBinder.bind((OuterJoinExpression) 
segment, parentSegmentType, binderContext, tableBinderContexts, 
outerTableBinderContexts);
+        }
         // TODO support more ExpressionSegment bound
         return segment;
     }
diff --git 
a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/type/OuterJoinExpressionBinder.java
 
b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/type/OuterJoinExpressionBinder.java
new file mode 100644
index 00000000000..06eda64bb82
--- /dev/null
+++ 
b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/type/OuterJoinExpressionBinder.java
@@ -0,0 +1,52 @@
+/*
+ * 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.shardingsphere.infra.binder.engine.segment.dml.expression.type;
+
+import com.cedarsoftware.util.CaseInsensitiveMap.CaseInsensitiveString;
+import com.google.common.collect.Multimap;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.shardingsphere.infra.binder.engine.segment.SegmentType;
+import 
org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.TableSegmentBinderContext;
+import 
org.apache.shardingsphere.infra.binder.engine.statement.SQLStatementBinderContext;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.join.OuterJoinExpression;
+
+/**
+ * Outer join expression binder.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class OuterJoinExpressionBinder {
+    
+    /**
+     * Bind outer join expression.
+     *
+     * @param segment outer join expression segment
+     * @param parentSegmentType parent segment type
+     * @param binderContext SQL statement binder context
+     * @param tableBinderContexts table binder contexts
+     * @param outerTableBinderContexts outer table binder contexts
+     * @return bound case when expression
+     */
+    public static OuterJoinExpression bind(final OuterJoinExpression segment, 
final SegmentType parentSegmentType,
+                                           final SQLStatementBinderContext 
binderContext, final Multimap<CaseInsensitiveString, TableSegmentBinderContext> 
tableBinderContexts,
+                                           final 
Multimap<CaseInsensitiveString, TableSegmentBinderContext> 
outerTableBinderContexts) {
+        ColumnSegment boundColumnName = 
ColumnSegmentBinder.bind(segment.getColumnName(), parentSegmentType, 
binderContext, tableBinderContexts, outerTableBinderContexts);
+        return new OuterJoinExpression(segment.getStartIndex(), 
segment.getStopIndex(), boundColumnName, segment.getJoinOperator(), 
segment.getText());
+    }
+}
diff --git 
a/infra/binder/src/test/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/type/OuterJoinExpressionBinderTest.java
 
b/infra/binder/src/test/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/type/OuterJoinExpressionBinderTest.java
new file mode 100644
index 00000000000..bf13805a2b7
--- /dev/null
+++ 
b/infra/binder/src/test/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/type/OuterJoinExpressionBinderTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.shardingsphere.infra.binder.engine.segment.dml.expression.type;
+
+import com.cedarsoftware.util.CaseInsensitiveMap;
+import com.cedarsoftware.util.CaseInsensitiveMap.CaseInsensitiveString;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import org.apache.shardingsphere.infra.binder.engine.segment.SegmentType;
+import 
org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.TableSegmentBinderContext;
+import 
org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.type.SimpleTableSegmentBinderContext;
+import 
org.apache.shardingsphere.infra.binder.engine.statement.SQLStatementBinderContext;
+import 
org.apache.shardingsphere.sql.parser.statement.core.enums.TableSourceType;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ColumnProjectionSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.join.OuterJoinExpression;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.OwnerSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.bound.ColumnSegmentBoundInfo;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.bound.TableSegmentBoundInfo;
+import 
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+class OuterJoinExpressionBinderTest {
+    
+    @Test
+    public void assertBindOuterJoinExpression() {
+        Multimap<CaseInsensitiveString, TableSegmentBinderContext> 
tableBinderContexts = LinkedHashMultimap.create();
+        ColumnSegment boundOrderIdColumn = new ColumnSegment(0, 0, new 
IdentifierValue("order_id"));
+        TableSegmentBoundInfo tableBoundInfo = new TableSegmentBoundInfo(new 
IdentifierValue("t_order"), new IdentifierValue("order_id"));
+        boundOrderIdColumn.setColumnBoundInfo(new 
ColumnSegmentBoundInfo(tableBoundInfo,
+                new IdentifierValue("t_order"), new 
IdentifierValue("order_id"), TableSourceType.PHYSICAL_TABLE));
+        tableBinderContexts.put(new 
CaseInsensitiveMap.CaseInsensitiveString("t_order"),
+                new SimpleTableSegmentBinderContext(Collections.singleton(new 
ColumnProjectionSegment(boundOrderIdColumn)), TableSourceType.PHYSICAL_TABLE));
+        ColumnSegment column = new ColumnSegment(0, 0, new 
IdentifierValue("order_id"));
+        column.setOwner(new OwnerSegment(0, 0, new 
IdentifierValue("t_order")));
+        OuterJoinExpression originalExpression = new OuterJoinExpression(0, 0, 
column, "+", "t_order.order_id(+)");
+        OuterJoinExpression actual = OuterJoinExpressionBinder.bind(
+                originalExpression, SegmentType.PREDICATE, 
mock(SQLStatementBinderContext.class), tableBinderContexts, 
LinkedHashMultimap.create());
+        assertThat(actual.getStartIndex(), 
is(originalExpression.getStartIndex()));
+        assertThat(actual.getStopIndex(), 
is(originalExpression.getStopIndex()));
+        assertThat(actual.getColumnName().getIdentifier().getValue(), 
is("order_id"));
+        assertThat(actual.getJoinOperator(), is("+"));
+        assertThat(actual.getText(), is("t_order.order_id(+)"));
+    }
+}
diff --git 
a/test/it/binder/src/test/java/org/apache/shardingsphere/test/it/sql/binder/dialect/oracle/OracleBinderIT.java
 
b/test/it/binder/src/test/java/org/apache/shardingsphere/test/it/sql/binder/dialect/oracle/OracleBinderIT.java
new file mode 100644
index 00000000000..415a2824967
--- /dev/null
+++ 
b/test/it/binder/src/test/java/org/apache/shardingsphere/test/it/sql/binder/dialect/oracle/OracleBinderIT.java
@@ -0,0 +1,25 @@
+/*
+ * 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.shardingsphere.test.it.sql.binder.dialect.oracle;
+
+import org.apache.shardingsphere.test.it.sql.binder.SQLBinderIT;
+import org.apache.shardingsphere.test.it.sql.binder.SQLBinderITSettings;
+
+@SQLBinderITSettings("Oracle")
+class OracleBinderIT extends SQLBinderIT {
+}
diff --git a/test/it/binder/src/test/resources/cases/dml/select.xml 
b/test/it/binder/src/test/resources/cases/dml/select.xml
index 17714f959a3..a770e1414a3 100644
--- a/test/it/binder/src/test/resources/cases/dml/select.xml
+++ b/test/it/binder/src/test/resources/cases/dml/select.xml
@@ -708,4 +708,66 @@
             </simple-table>
         </from>
     </select>
+    
+    <select sql-case-id="select_with_outer_join_operator">
+        <from start-index="23" stop-index="47">
+            <join-table join-type="COMMA" start-index="23" stop-index="47">
+                <left start-index="23" stop-index="31">
+                    <simple-table alias="o" name="t_order" start-index="23" 
stop-index="31">
+                        <table-bound start-index="0" stop-index="0">
+                            <original-database name="foo_db_1" start-index="0" 
stop-index="0"/>
+                            <original-schema name="foo_db_1" start-index="0" 
stop-index="0"/>
+                        </table-bound>
+                    </simple-table>
+                </left>
+                <right start-index="34" stop-index="47">
+                    <simple-table alias="i" name="t_order_item" 
start-index="34" stop-index="47">
+                        <table-bound start-index="0" stop-index="0">
+                            <original-database name="foo_db_1" start-index="0" 
stop-index="0"/>
+                            <original-schema name="foo_db_1" start-index="0" 
stop-index="0"/>
+                        </table-bound>
+                    </simple-table>
+                </right>
+            </join-table>
+        </from>
+        <projections start-index="7" stop-index="16">
+            <column-projection name="order_id" start-index="7" stop-index="16">
+                <owner name="o" start-index="7" stop-index="7"/>
+            </column-projection>
+        </projections>
+        <where start-index="49" stop-index="80">
+            <expr start-index="55" stop-index="80">
+                <binary-operation-expression start-index="55" stop-index="80">
+                    <left start-index="55" stop-index="64">
+                        <column name="order_id" start-index="55" 
stop-index="64">
+                            <owner name="o" start-index="55" stop-index="55"/>
+                            <column-bound start-index="0" stop-index="0">
+                                <original-database name="foo_db_1" 
start-index="0" stop-index="0"/>
+                                <original-schema name="foo_db_1" 
start-index="0" stop-index="0"/>
+                                <original-table name="t_order" start-index="0" 
stop-index="0"/>
+                                <original-column name="order_id" 
start-delimiter="&quot;" end-delimiter="&quot;" start-index="0" stop-index="0"/>
+                                <table-source-type name="PHYSICAL_TABLE" 
start-index="0" stop-index="0"/>
+                            </column-bound>
+                        </column>
+                    </left>
+                    <operator>=</operator>
+                    <right start-index="68" stop-index="80">
+                        <outer-join-expression text="i.order_id(+)" 
start-index="68" stop-index="80">
+                            <column name="order_id" start-index="68" 
stop-index="77">
+                                <owner name="i" start-index="68" 
stop-index="68"/>
+                                <column-bound start-index="0" stop-index="0">
+                                    <original-database name="foo_db_1" 
start-index="0" stop-index="0"/>
+                                    <original-schema name="foo_db_1" 
start-index="0" stop-index="0"/>
+                                    <original-table name="t_order_item" 
start-index="0" stop-index="0"/>
+                                    <original-column name="order_id" 
start-delimiter="&quot;" end-delimiter="&quot;" start-index="0" stop-index="0"/>
+                                    <table-source-type name="PHYSICAL_TABLE" 
start-index="0" stop-index="0"/>
+                                </column-bound>
+                            </column>
+                            <join-operator>(+)</join-operator>
+                        </outer-join-expression>
+                    </right>
+                </binary-operation-expression>
+            </expr>
+        </where>
+    </select>
 </sql-parser-test-cases>
diff --git a/test/it/binder/src/test/resources/sqls/dml/select.xml 
b/test/it/binder/src/test/resources/sqls/dml/select.xml
index 608c664bd20..ff5637d1280 100644
--- a/test/it/binder/src/test/resources/sqls/dml/select.xml
+++ b/test/it/binder/src/test/resources/sqls/dml/select.xml
@@ -23,4 +23,5 @@
     <sql-case id="select_with_clause_with_multiple_cte_definitions" 
value="WITH cte1(status, user_id) AS (SELECT status, user_id FROM t_order a), 
cte2(user_id, item_id) AS (SELECT user_id, item_id FROM t_order_item b) SELECT 
status, user_id, item_id FROM cte1 INNER JOIN cte2 ON cte1.user_id = 
cte2.user_id" db-types="MySQL" />
     <sql-case id="select_with_with_clause_with_column_definition" value="WITH 
t_order_tmp (col1, col2, col3, col4, col5, col6) AS (SELECT * FROM t_order o) 
SELECT col1 FROM t_order_tmp" db-types="MySQL"/>
     <sql-case id="select_with_current_select_projection_reference" 
value="SELECT order_id AS orderId, (SELECT orderId) AS tempOrderId FROM 
t_order" db-types="MySQL"/>
+    <sql-case id="select_with_outer_join_operator" value="SELECT o.order_id 
FROM t_order o, t_order_item i WHERE o.order_id = i.order_id(+)" 
db-types="Oracle"/>
 </sql-cases>

Reply via email to