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=""" 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>