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 <[email protected]>
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=""" end-delimiter=""" 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=""" end-delimiter=""" 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>