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 4515826080a Add check for select with union all routing to multi data sources (#35037) 4515826080a is described below commit 4515826080a5f19b20b816d25b7d78025f7272f3 Author: ZhangCheng <chengzh...@apache.org> AuthorDate: Wed Mar 19 16:33:54 2025 +0800 Add check for select with union all routing to multi data sources (#35037) * Add check for select with union all routing to multi data sources * Add check for select with union all routing to multi data sources --- RELEASE-NOTES.md | 1 + ...ectMultipleDataSourcesWithCombineException.java | 33 ++++++++ .../ShardingRouteContextCheckerFactory.java | 5 ++ .../dml/ShardingSelectRouteContextChecker.java | 57 +++++++++++++ .../dml/ShardingSelectRouteContextCheckerTest.java | 97 ++++++++++++++++++++++ 5 files changed, 193 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 6a1428145a5..beb32e75200 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -27,6 +27,7 @@ 1. SQL Parser: Support MySQL DELETE with statement parse - [#34817](https://github.com/apache/shardingsphere/pull/34817) 1. Encrypt: Use EncryptDerivedColumnSuffix to enhance encrypt table subquery rewrite logic - [#34829](https://github.com/apache/shardingsphere/pull/34829) 1. Encrypt: Add quotes to encrypt rewrite derived columns - [#34950](https://github.com/apache/shardingsphere/pull/34950) +1. SQL Router: Add check for select with union all routing to multi data sources - [#35037](https://github.com/apache/shardingsphere/pull/35037) ### Bug Fixes diff --git a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/exception/syntax/SelectMultipleDataSourcesWithCombineException.java b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/exception/syntax/SelectMultipleDataSourcesWithCombineException.java new file mode 100644 index 00000000000..dcb43611ee6 --- /dev/null +++ b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/exception/syntax/SelectMultipleDataSourcesWithCombineException.java @@ -0,0 +1,33 @@ +/* + * 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.sharding.exception.syntax; + +import org.apache.shardingsphere.infra.exception.core.external.sql.sqlstate.XOpenSQLState; +import org.apache.shardingsphere.sharding.exception.ShardingSQLException; + +/** + * Select multiple data sources with combine Exception. + */ +public final class SelectMultipleDataSourcesWithCombineException extends ShardingSQLException { + + private static final long serialVersionUID = 6817787033278995625L; + + public SelectMultipleDataSourcesWithCombineException(final String operation) { + super(XOpenSQLState.SYNTAX_ERROR, 36, "SELECT ... %s can not support route to multiple data sources.", operation); + } +} diff --git a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/checker/ShardingRouteContextCheckerFactory.java b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/checker/ShardingRouteContextCheckerFactory.java index 8b0aa3ff1da..6e40d8d150c 100644 --- a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/checker/ShardingRouteContextCheckerFactory.java +++ b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/checker/ShardingRouteContextCheckerFactory.java @@ -28,6 +28,7 @@ import org.apache.shardingsphere.sharding.route.engine.checker.ddl.ShardingPrepa import org.apache.shardingsphere.sharding.route.engine.checker.ddl.ShardingRenameTableRouteContextChecker; import org.apache.shardingsphere.sharding.route.engine.checker.dml.ShardingDeleteRouteContextChecker; import org.apache.shardingsphere.sharding.route.engine.checker.dml.ShardingInsertRouteContextChecker; +import org.apache.shardingsphere.sharding.route.engine.checker.dml.ShardingSelectRouteContextChecker; import org.apache.shardingsphere.sharding.route.engine.checker.dml.ShardingUpdateRouteContextChecker; import org.apache.shardingsphere.sharding.route.engine.condition.ShardingConditions; import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement; @@ -42,6 +43,7 @@ import org.apache.shardingsphere.sql.parser.statement.core.statement.ddl.RenameT import org.apache.shardingsphere.sql.parser.statement.core.statement.dml.DMLStatement; import org.apache.shardingsphere.sql.parser.statement.core.statement.dml.DeleteStatement; import org.apache.shardingsphere.sql.parser.statement.core.statement.dml.InsertStatement; +import org.apache.shardingsphere.sql.parser.statement.core.statement.dml.SelectStatement; import org.apache.shardingsphere.sql.parser.statement.core.statement.dml.UpdateStatement; import java.util.Optional; @@ -104,6 +106,9 @@ public final class ShardingRouteContextCheckerFactory { if (sqlStatement instanceof DeleteStatement) { return Optional.of(new ShardingDeleteRouteContextChecker()); } + if (sqlStatement instanceof SelectStatement) { + return Optional.of(new ShardingSelectRouteContextChecker()); + } return Optional.empty(); } } diff --git a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/checker/dml/ShardingSelectRouteContextChecker.java b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/checker/dml/ShardingSelectRouteContextChecker.java new file mode 100644 index 00000000000..882e89f2259 --- /dev/null +++ b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/checker/dml/ShardingSelectRouteContextChecker.java @@ -0,0 +1,57 @@ +/* + * 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.sharding.route.engine.checker.dml; + +import org.apache.shardingsphere.infra.config.props.ConfigurationProperties; +import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase; +import org.apache.shardingsphere.infra.route.context.RouteContext; +import org.apache.shardingsphere.infra.route.context.RouteUnit; +import org.apache.shardingsphere.infra.session.query.QueryContext; +import org.apache.shardingsphere.sharding.exception.syntax.SelectMultipleDataSourcesWithCombineException; +import org.apache.shardingsphere.sharding.route.engine.checker.ShardingRouteContextChecker; +import org.apache.shardingsphere.sharding.rule.ShardingRule; +import org.apache.shardingsphere.sql.parser.statement.core.statement.dml.SelectStatement; + +/** + * Sharding select route context checker. + */ +public final class ShardingSelectRouteContextChecker implements ShardingRouteContextChecker { + + @Override + public void check(final ShardingRule shardingRule, final QueryContext queryContext, final ShardingSphereDatabase database, final ConfigurationProperties props, final RouteContext routeContext) { + SelectStatement selectStatement = (SelectStatement) queryContext.getSqlStatementContext().getSqlStatement(); + if (selectStatement.getCombine().isPresent() && !isRouteToSingleDataSource(routeContext)) { + throw new SelectMultipleDataSourcesWithCombineException(selectStatement.getCombine().get().getCombineType().name()); + } + } + + private boolean isRouteToSingleDataSource(final RouteContext routeContext) { + if (routeContext.getRouteUnits().isEmpty()) { + return true; + } + boolean result = true; + String sampleDataSourceName = routeContext.getRouteUnits().iterator().next().getDataSourceMapper().getLogicName(); + for (RouteUnit each : routeContext.getRouteUnits()) { + if (!each.getDataSourceMapper().getLogicName().equals(sampleDataSourceName)) { + result = false; + break; + } + } + return result; + } +} diff --git a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/checker/dml/ShardingSelectRouteContextCheckerTest.java b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/checker/dml/ShardingSelectRouteContextCheckerTest.java new file mode 100644 index 00000000000..fbedbe881ac --- /dev/null +++ b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/checker/dml/ShardingSelectRouteContextCheckerTest.java @@ -0,0 +1,97 @@ +/* + * 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.sharding.route.engine.checker.dml; + +import org.apache.shardingsphere.infra.binder.context.statement.dml.SelectStatementContext; +import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase; +import org.apache.shardingsphere.infra.route.context.RouteContext; +import org.apache.shardingsphere.infra.route.context.RouteMapper; +import org.apache.shardingsphere.infra.route.context.RouteUnit; +import org.apache.shardingsphere.infra.session.query.QueryContext; +import org.apache.shardingsphere.sharding.exception.syntax.SelectMultipleDataSourcesWithCombineException; +import org.apache.shardingsphere.sharding.rule.ShardingRule; +import org.apache.shardingsphere.sql.parser.statement.core.enums.CombineType; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.combine.CombineSegment; +import org.apache.shardingsphere.sql.parser.statement.core.statement.dml.SelectStatement; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ShardingSelectRouteContextCheckerTest { + + @Mock + private ShardingRule shardingRule; + + @Mock + private QueryContext queryContext; + + @Mock + private RouteContext routeContext; + + @Mock + private ShardingSphereDatabase database; + + @Test + void assertCombineExistsAndRoutesToMultipleDataSources() { + SelectStatement selectStatement = mock(SelectStatement.class); + CombineSegment combineSegment = mock(CombineSegment.class); + when(combineSegment.getCombineType()).thenReturn(CombineType.UNION_ALL); + when(selectStatement.getCombine()).thenReturn(Optional.of(combineSegment)); + SelectStatementContext selectContext = mock(SelectStatementContext.class); + when(selectContext.getSqlStatement()).thenReturn(selectStatement); + when(queryContext.getSqlStatementContext()).thenReturn(selectContext); + when(routeContext.getRouteUnits()).thenReturn(Arrays.asList(createMockRouteUnit("ds1"), createMockRouteUnit("ds2"))); + assertThrows(SelectMultipleDataSourcesWithCombineException.class, () -> new ShardingSelectRouteContextChecker().check(shardingRule, queryContext, database, mock(), routeContext)); + } + + @Test + void assertCombineExistsAndRoutesToSingleDataSource() { + SelectStatement selectStatement = mock(SelectStatement.class); + when(selectStatement.getCombine()).thenReturn(Optional.of(mock(CombineSegment.class))); + SelectStatementContext selectContext = mock(SelectStatementContext.class); + when(selectContext.getSqlStatement()).thenReturn(selectStatement); + when(queryContext.getSqlStatementContext()).thenReturn(selectContext); + when(routeContext.getRouteUnits()).thenReturn(Arrays.asList(createMockRouteUnit("ds1"), createMockRouteUnit("ds1"))); + assertDoesNotThrow(() -> new ShardingSelectRouteContextChecker().check(shardingRule, queryContext, database, mock(), routeContext)); + } + + @Test + void assertNoCombineExists() { + SelectStatement selectStatement = mock(SelectStatement.class); + when(selectStatement.getCombine()).thenReturn(Optional.empty()); + SelectStatementContext selectContext = mock(SelectStatementContext.class); + when(selectContext.getSqlStatement()).thenReturn(selectStatement); + when(queryContext.getSqlStatementContext()).thenReturn(selectContext); + assertDoesNotThrow(() -> new ShardingSelectRouteContextChecker().check(shardingRule, queryContext, database, mock(), routeContext)); + } + + private RouteUnit createMockRouteUnit(final String dataSourceName) { + return new RouteUnit(new RouteMapper(dataSourceName, dataSourceName), Collections.emptyList()); + } +}