Skip to content

Commit 2572ae8

Browse files
authored
Support outer join expression bind (#35019)
* Support outer join expression bind * Support outer join expression bind
1 parent 40b6b79 commit 2572ae8

File tree

7 files changed

+212
-0
lines changed

7 files changed

+212
-0
lines changed

RELEASE-NOTES.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
1. SQL Binder: Support select aggregation function sql bind in projection and having - [#34379](https://github.com/apache/shardingsphere/pull/34379)
2121
1. SQL Binder: Support column definition for the WITH clause and ExternalTableBinderContext in CommonTableExpressionBinder.[#34384](https://github.com/apache/shardingsphere/pull/34384)
2222
1. SQL Binder: Support case when then else segment bind - [#34600](https://github.com/apache/shardingsphere/pull/34600)
23+
1. SQL Binder: Support outer join expression bind - [#35019](https://github.com/apache/shardingsphere/pull/35019)
2324
1. SQL Parser: Support MySQL SELECT CAST AS YEAR statement parse - [#34638](https://github.com/apache/shardingsphere/pull/34638)
2425
1. SQL Parser: Support MySQL SELECT MAX(ALL expr) statement parse - [#34639](https://github.com/apache/shardingsphere/pull/34639)
2526
1. SQL Parser: Support MySQL INSERT with GEOMCOLLECTION function parse - [#34654](https://github.com/apache/shardingsphere/pull/34654)

infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/expression/ExpressionSegmentBinder.java

+5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type.FunctionExpressionSegmentBinder;
3434
import org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type.InExpressionBinder;
3535
import org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type.NotExpressionBinder;
36+
import org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type.OuterJoinExpressionBinder;
3637
import org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type.SubquerySegmentBinder;
3738
import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.TableSegmentBinderContext;
3839
import org.apache.shardingsphere.infra.binder.engine.statement.SQLStatementBinderContext;
@@ -48,6 +49,7 @@
4849
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.subquery.SubqueryExpressionSegment;
4950
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.AggregationDistinctProjectionSegment;
5051
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.AggregationProjectionSegment;
52+
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.join.OuterJoinExpression;
5153

5254
/**
5355
* Expression segment binder.
@@ -104,6 +106,9 @@ public static ExpressionSegment bind(final ExpressionSegment segment, final Segm
104106
if (segment instanceof CaseWhenExpression) {
105107
return CaseWhenExpressionBinder.bind((CaseWhenExpression) segment, parentSegmentType, binderContext, tableBinderContexts, outerTableBinderContexts);
106108
}
109+
if (segment instanceof OuterJoinExpression) {
110+
return OuterJoinExpressionBinder.bind((OuterJoinExpression) segment, parentSegmentType, binderContext, tableBinderContexts, outerTableBinderContexts);
111+
}
107112
// TODO support more ExpressionSegment bound
108113
return segment;
109114
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type;
19+
20+
import com.cedarsoftware.util.CaseInsensitiveMap.CaseInsensitiveString;
21+
import com.google.common.collect.Multimap;
22+
import lombok.AccessLevel;
23+
import lombok.NoArgsConstructor;
24+
import org.apache.shardingsphere.infra.binder.engine.segment.SegmentType;
25+
import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.TableSegmentBinderContext;
26+
import org.apache.shardingsphere.infra.binder.engine.statement.SQLStatementBinderContext;
27+
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
28+
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.join.OuterJoinExpression;
29+
30+
/**
31+
* Outer join expression binder.
32+
*/
33+
@NoArgsConstructor(access = AccessLevel.PRIVATE)
34+
public final class OuterJoinExpressionBinder {
35+
36+
/**
37+
* Bind outer join expression.
38+
*
39+
* @param segment outer join expression segment
40+
* @param parentSegmentType parent segment type
41+
* @param binderContext SQL statement binder context
42+
* @param tableBinderContexts table binder contexts
43+
* @param outerTableBinderContexts outer table binder contexts
44+
* @return bound case when expression
45+
*/
46+
public static OuterJoinExpression bind(final OuterJoinExpression segment, final SegmentType parentSegmentType,
47+
final SQLStatementBinderContext binderContext, final Multimap<CaseInsensitiveString, TableSegmentBinderContext> tableBinderContexts,
48+
final Multimap<CaseInsensitiveString, TableSegmentBinderContext> outerTableBinderContexts) {
49+
ColumnSegment boundColumnName = ColumnSegmentBinder.bind(segment.getColumnName(), parentSegmentType, binderContext, tableBinderContexts, outerTableBinderContexts);
50+
return new OuterJoinExpression(segment.getStartIndex(), segment.getStopIndex(), boundColumnName, segment.getJoinOperator(), segment.getText());
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.shardingsphere.infra.binder.engine.segment.dml.expression.type;
19+
20+
import com.cedarsoftware.util.CaseInsensitiveMap;
21+
import com.cedarsoftware.util.CaseInsensitiveMap.CaseInsensitiveString;
22+
import com.google.common.collect.LinkedHashMultimap;
23+
import com.google.common.collect.Multimap;
24+
import org.apache.shardingsphere.infra.binder.engine.segment.SegmentType;
25+
import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.TableSegmentBinderContext;
26+
import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.type.SimpleTableSegmentBinderContext;
27+
import org.apache.shardingsphere.infra.binder.engine.statement.SQLStatementBinderContext;
28+
import org.apache.shardingsphere.sql.parser.statement.core.enums.TableSourceType;
29+
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
30+
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ColumnProjectionSegment;
31+
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.join.OuterJoinExpression;
32+
import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.OwnerSegment;
33+
import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.bound.ColumnSegmentBoundInfo;
34+
import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.bound.TableSegmentBoundInfo;
35+
import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
36+
import org.junit.jupiter.api.Test;
37+
38+
import java.util.Collections;
39+
40+
import static org.hamcrest.CoreMatchers.is;
41+
import static org.hamcrest.MatcherAssert.assertThat;
42+
import static org.mockito.Mockito.mock;
43+
44+
class OuterJoinExpressionBinderTest {
45+
46+
@Test
47+
public void assertBindOuterJoinExpression() {
48+
Multimap<CaseInsensitiveString, TableSegmentBinderContext> tableBinderContexts = LinkedHashMultimap.create();
49+
ColumnSegment boundOrderIdColumn = new ColumnSegment(0, 0, new IdentifierValue("order_id"));
50+
TableSegmentBoundInfo tableBoundInfo = new TableSegmentBoundInfo(new IdentifierValue("t_order"), new IdentifierValue("order_id"));
51+
boundOrderIdColumn.setColumnBoundInfo(new ColumnSegmentBoundInfo(tableBoundInfo,
52+
new IdentifierValue("t_order"), new IdentifierValue("order_id"), TableSourceType.PHYSICAL_TABLE));
53+
tableBinderContexts.put(new CaseInsensitiveMap.CaseInsensitiveString("t_order"),
54+
new SimpleTableSegmentBinderContext(Collections.singleton(new ColumnProjectionSegment(boundOrderIdColumn)), TableSourceType.PHYSICAL_TABLE));
55+
ColumnSegment column = new ColumnSegment(0, 0, new IdentifierValue("order_id"));
56+
column.setOwner(new OwnerSegment(0, 0, new IdentifierValue("t_order")));
57+
OuterJoinExpression originalExpression = new OuterJoinExpression(0, 0, column, "+", "t_order.order_id(+)");
58+
OuterJoinExpression actual = OuterJoinExpressionBinder.bind(
59+
originalExpression, SegmentType.PREDICATE, mock(SQLStatementBinderContext.class), tableBinderContexts, LinkedHashMultimap.create());
60+
assertThat(actual.getStartIndex(), is(originalExpression.getStartIndex()));
61+
assertThat(actual.getStopIndex(), is(originalExpression.getStopIndex()));
62+
assertThat(actual.getColumnName().getIdentifier().getValue(), is("order_id"));
63+
assertThat(actual.getJoinOperator(), is("+"));
64+
assertThat(actual.getText(), is("t_order.order_id(+)"));
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.shardingsphere.test.it.sql.binder.dialect.oracle;
19+
20+
import org.apache.shardingsphere.test.it.sql.binder.SQLBinderIT;
21+
import org.apache.shardingsphere.test.it.sql.binder.SQLBinderITSettings;
22+
23+
@SQLBinderITSettings("Oracle")
24+
class OracleBinderIT extends SQLBinderIT {
25+
}

test/it/binder/src/test/resources/cases/dml/select.xml

+62
Original file line numberDiff line numberDiff line change
@@ -708,4 +708,66 @@
708708
</simple-table>
709709
</from>
710710
</select>
711+
712+
<select sql-case-id="select_with_outer_join_operator">
713+
<from start-index="23" stop-index="47">
714+
<join-table join-type="COMMA" start-index="23" stop-index="47">
715+
<left start-index="23" stop-index="31">
716+
<simple-table alias="o" name="t_order" start-index="23" stop-index="31">
717+
<table-bound start-index="0" stop-index="0">
718+
<original-database name="foo_db_1" start-index="0" stop-index="0"/>
719+
<original-schema name="foo_db_1" start-index="0" stop-index="0"/>
720+
</table-bound>
721+
</simple-table>
722+
</left>
723+
<right start-index="34" stop-index="47">
724+
<simple-table alias="i" name="t_order_item" start-index="34" stop-index="47">
725+
<table-bound start-index="0" stop-index="0">
726+
<original-database name="foo_db_1" start-index="0" stop-index="0"/>
727+
<original-schema name="foo_db_1" start-index="0" stop-index="0"/>
728+
</table-bound>
729+
</simple-table>
730+
</right>
731+
</join-table>
732+
</from>
733+
<projections start-index="7" stop-index="16">
734+
<column-projection name="order_id" start-index="7" stop-index="16">
735+
<owner name="o" start-index="7" stop-index="7"/>
736+
</column-projection>
737+
</projections>
738+
<where start-index="49" stop-index="80">
739+
<expr start-index="55" stop-index="80">
740+
<binary-operation-expression start-index="55" stop-index="80">
741+
<left start-index="55" stop-index="64">
742+
<column name="order_id" start-index="55" stop-index="64">
743+
<owner name="o" start-index="55" stop-index="55"/>
744+
<column-bound start-index="0" stop-index="0">
745+
<original-database name="foo_db_1" start-index="0" stop-index="0"/>
746+
<original-schema name="foo_db_1" start-index="0" stop-index="0"/>
747+
<original-table name="t_order" start-index="0" stop-index="0"/>
748+
<original-column name="order_id" start-delimiter="&quot;" end-delimiter="&quot;" start-index="0" stop-index="0"/>
749+
<table-source-type name="PHYSICAL_TABLE" start-index="0" stop-index="0"/>
750+
</column-bound>
751+
</column>
752+
</left>
753+
<operator>=</operator>
754+
<right start-index="68" stop-index="80">
755+
<outer-join-expression text="i.order_id(+)" start-index="68" stop-index="80">
756+
<column name="order_id" start-index="68" stop-index="77">
757+
<owner name="i" start-index="68" stop-index="68"/>
758+
<column-bound start-index="0" stop-index="0">
759+
<original-database name="foo_db_1" start-index="0" stop-index="0"/>
760+
<original-schema name="foo_db_1" start-index="0" stop-index="0"/>
761+
<original-table name="t_order_item" start-index="0" stop-index="0"/>
762+
<original-column name="order_id" start-delimiter="&quot;" end-delimiter="&quot;" start-index="0" stop-index="0"/>
763+
<table-source-type name="PHYSICAL_TABLE" start-index="0" stop-index="0"/>
764+
</column-bound>
765+
</column>
766+
<join-operator>(+)</join-operator>
767+
</outer-join-expression>
768+
</right>
769+
</binary-operation-expression>
770+
</expr>
771+
</where>
772+
</select>
711773
</sql-parser-test-cases>

test/it/binder/src/test/resources/sqls/dml/select.xml

+1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@
2323
<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" />
2424
<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"/>
2525
<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"/>
26+
<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"/>
2627
</sql-cases>

0 commit comments

Comments
 (0)