This is an automated email from the ASF dual-hosted git repository.
zhangliang 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 c5b744d1db9 Add more test cases on
ShadowDMLStatementDataSourceMappingsRetrieverTest and ShadowSQLRouterTest
(#38234)
c5b744d1db9 is described below
commit c5b744d1db98d038ef0c849c5da6291b5fa7696e
Author: Liang Zhang <[email protected]>
AuthorDate: Fri Feb 27 12:56:40 2026 +0800
Add more test cases on ShadowDMLStatementDataSourceMappingsRetrieverTest
and ShadowSQLRouterTest (#38234)
---
.../shadow/route/ShadowSQLRouterTest.java | 90 +++++++++++
...MLStatementDataSourceMappingsRetrieverTest.java | 167 +++++++++++++++++++++
2 files changed, 257 insertions(+)
diff --git
a/features/shadow/core/src/test/java/org/apache/shardingsphere/shadow/route/ShadowSQLRouterTest.java
b/features/shadow/core/src/test/java/org/apache/shardingsphere/shadow/route/ShadowSQLRouterTest.java
new file mode 100644
index 00000000000..566f4777c41
--- /dev/null
+++
b/features/shadow/core/src/test/java/org/apache/shardingsphere/shadow/route/ShadowSQLRouterTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.shadow.route;
+
+import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
+import org.apache.shardingsphere.infra.route.SQLRouter;
+import org.apache.shardingsphere.infra.route.SQLRouter.Type;
+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.infra.spi.type.ordered.OrderedSPILoader;
+import
org.apache.shardingsphere.shadow.route.retriever.ShadowDataSourceMappingsRetriever;
+import
org.apache.shardingsphere.shadow.route.retriever.ShadowDataSourceMappingsRetrieverFactory;
+import org.apache.shardingsphere.shadow.rule.ShadowRule;
+import
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
+import
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(AutoMockExtension.class)
+@StaticMockSettings(ShadowDataSourceMappingsRetrieverFactory.class)
+class ShadowSQLRouterTest {
+
+ @Mock
+ private ShadowRule rule;
+
+ private ShadowSQLRouter sqlRouter;
+
+ @BeforeEach
+ void setUp() {
+ sqlRouter = (ShadowSQLRouter)
OrderedSPILoader.getServices(SQLRouter.class,
Collections.singleton(rule)).get(rule);
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("decorateRouteContextArguments")
+ void assertDecorateRouteContext(final String name, final String
routeActualDataSourceName, final String productionDataSourceName,
+ final Map<String, String>
shadowDataSourceMappings, final String expectedActualDataSourceName) {
+ QueryContext queryContext = mock(QueryContext.class);
+ ShadowDataSourceMappingsRetriever retriever =
mock(ShadowDataSourceMappingsRetriever.class);
+ when(retriever.retrieve(rule)).thenReturn(shadowDataSourceMappings);
+
when(ShadowDataSourceMappingsRetrieverFactory.newInstance(queryContext)).thenReturn(retriever);
+
when(rule.findProductionDataSourceName(routeActualDataSourceName)).thenReturn(null
== productionDataSourceName ? Optional.empty() :
Optional.of(productionDataSourceName));
+ RouteContext routeContext = new RouteContext();
+ routeContext.getRouteUnits().add(new RouteUnit(new
RouteMapper("logic_ds", routeActualDataSourceName), Collections.singleton(new
RouteMapper("t_order", "t_order_0"))));
+ sqlRouter.decorateRouteContext(routeContext, queryContext, mock(),
rule, Collections.singleton("t_order"), new ConfigurationProperties(new
Properties()));
+ RouteUnit actualRouteUnit =
routeContext.getRouteUnits().iterator().next();
+ assertThat(name,
actualRouteUnit.getDataSourceMapper().getActualName(),
is(expectedActualDataSourceName));
+ assertThat(actualRouteUnit.getDataSourceMapper().getLogicName(),
is("logic_ds"));
+ assertThat(actualRouteUnit.getTableMappers().size(), is(1));
+ assertThat(sqlRouter.getType(), is(Type.DATA_SOURCE));
+ }
+
+ private static Stream<Arguments> decorateRouteContextArguments() {
+ return Stream.of(
+ Arguments.of("skip route unit when production data source is
absent", "foo_ds", null, Collections.emptyMap(), "foo_ds"),
+ Arguments.of("replace route unit with shadow data source",
"foo_route_ds", "foo_prod_ds", Collections.singletonMap("foo_prod_ds",
"foo_shadow_ds"), "foo_shadow_ds"),
+ Arguments.of("replace route unit with production data source
when shadow mapping is absent", "foo_route_ds", "foo_prod_ds",
Collections.emptyMap(), "foo_prod_ds"));
+ }
+}
diff --git
a/features/shadow/core/src/test/java/org/apache/shardingsphere/shadow/route/retriever/dml/ShadowDMLStatementDataSourceMappingsRetrieverTest.java
b/features/shadow/core/src/test/java/org/apache/shardingsphere/shadow/route/retriever/dml/ShadowDMLStatementDataSourceMappingsRetrieverTest.java
new file mode 100644
index 00000000000..f3ff9ef178f
--- /dev/null
+++
b/features/shadow/core/src/test/java/org/apache/shardingsphere/shadow/route/retriever/dml/ShadowDMLStatementDataSourceMappingsRetrieverTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.shadow.route.retriever.dml;
+
+import lombok.SneakyThrows;
+import
org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
+import
org.apache.shardingsphere.infra.binder.context.statement.type.dml.DeleteStatementContext;
+import
org.apache.shardingsphere.infra.binder.context.statement.type.dml.InsertStatementContext;
+import
org.apache.shardingsphere.infra.binder.context.statement.type.dml.SelectStatementContext;
+import
org.apache.shardingsphere.infra.binder.context.statement.type.dml.UpdateStatementContext;
+import org.apache.shardingsphere.infra.hint.HintValueContext;
+import org.apache.shardingsphere.infra.session.connection.ConnectionContext;
+import org.apache.shardingsphere.infra.session.query.QueryContext;
+import
org.apache.shardingsphere.shadow.route.retriever.dml.table.column.ShadowColumnDataSourceMappingsRetriever;
+import
org.apache.shardingsphere.shadow.route.retriever.dml.table.column.impl.ShadowDeleteStatementDataSourceMappingsRetriever;
+import
org.apache.shardingsphere.shadow.route.retriever.dml.table.column.impl.ShadowInsertStatementDataSourceMappingsRetriever;
+import
org.apache.shardingsphere.shadow.route.retriever.dml.table.column.impl.ShadowSelectStatementDataSourceMappingsRetriever;
+import
org.apache.shardingsphere.shadow.route.retriever.dml.table.column.impl.ShadowUpdateStatementDataSourceMappingsRetriever;
+import
org.apache.shardingsphere.shadow.route.retriever.dml.table.hint.ShadowTableHintDataSourceMappingsRetriever;
+import org.apache.shardingsphere.shadow.rule.ShadowRule;
+import org.apache.shardingsphere.shadow.spi.ShadowOperationType;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.DeleteStatement;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.InsertStatement;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.UpdateStatement;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.internal.configuration.plugins.Plugins;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isA;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ShadowDMLStatementDataSourceMappingsRetrieverTest {
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("shadowColumnRetrieverTypeArguments")
+ void assertShadowColumnRetrieverType(final String name, final
SQLStatementContext sqlStatementContext, final Class<?> expectedRetrieverType)
throws ReflectiveOperationException {
+ Object actualShadowColumnRetriever = Plugins.getMemberAccessor().get(
+
ShadowDMLStatementDataSourceMappingsRetriever.class.getDeclaredField("shadowColumnDataSourceMappingsRetriever"),
+ new
ShadowDMLStatementDataSourceMappingsRetriever(createQueryContext(sqlStatementContext),
ShadowOperationType.INSERT));
+ if (null == expectedRetrieverType) {
+ assertNull(actualShadowColumnRetriever);
+ } else {
+ assertThat(actualShadowColumnRetriever,
isA(expectedRetrieverType));
+ }
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("retrieveArguments")
+ void assertRetrieve(final String name,
+ final Map<String, String> tableHintResult, final
Map<String, String> columnRetrieverResult, final boolean
hasShadowColumnRetriever, final Map<String, String> expected) {
+ ShadowDMLStatementDataSourceMappingsRetriever retriever = new
ShadowDMLStatementDataSourceMappingsRetriever(createQueryContext(createOtherSqlStatementContext()),
ShadowOperationType.INSERT);
+ ShadowRule rule = mock(ShadowRule.class);
+ Collection<String> shadowTables = Collections.singleton("t_order");
+ ShadowTableHintDataSourceMappingsRetriever tableHintRetriever =
mock(ShadowTableHintDataSourceMappingsRetriever.class);
+ setField(retriever, "tableHintDataSourceMappingsRetriever",
tableHintRetriever);
+
when(rule.filterShadowTables(Collections.singleton("t_order"))).thenReturn(shadowTables);
+ when(tableHintRetriever.retrieve(rule,
shadowTables)).thenReturn(tableHintResult);
+ if (hasShadowColumnRetriever) {
+ ShadowColumnDataSourceMappingsRetriever shadowColumnRetriever =
mock(ShadowColumnDataSourceMappingsRetriever.class);
+ when(shadowColumnRetriever.retrieve(rule,
shadowTables)).thenReturn(columnRetrieverResult);
+ setField(retriever, "shadowColumnDataSourceMappingsRetriever",
shadowColumnRetriever);
+ } else {
+ setField(retriever, "shadowColumnDataSourceMappingsRetriever",
null);
+ }
+ Map<String, String> actual = retriever.retrieve(rule);
+ assertThat(name, actual, is(expected));
+ }
+
+ private QueryContext createQueryContext(final SQLStatementContext
sqlStatementContext) {
+ ConnectionContext connectionContext = mock(ConnectionContext.class);
+
when(connectionContext.getCurrentDatabaseName()).thenReturn(Optional.of("foo_db"));
+ return new QueryContext(sqlStatementContext, "",
Collections.emptyList(), new HintValueContext(), connectionContext, mock());
+ }
+
+ @SneakyThrows(ReflectiveOperationException.class)
+ private static void setField(final
ShadowDMLStatementDataSourceMappingsRetriever target, final String fieldName,
final Object fieldValue) {
+
Plugins.getMemberAccessor().set(ShadowDMLStatementDataSourceMappingsRetriever.class.getDeclaredField(fieldName),
target, fieldValue);
+ }
+
+ private static Stream<Arguments> shadowColumnRetrieverTypeArguments() {
+ return Stream.of(
+ Arguments.of("insert statement context",
createInsertSqlStatementContext(),
ShadowInsertStatementDataSourceMappingsRetriever.class),
+ Arguments.of("delete statement context",
createDeleteSqlStatementContext(),
ShadowDeleteStatementDataSourceMappingsRetriever.class),
+ Arguments.of("update statement context",
createUpdateSqlStatementContext(),
ShadowUpdateStatementDataSourceMappingsRetriever.class),
+ Arguments.of("select statement context",
createSelectSqlStatementContext(),
ShadowSelectStatementDataSourceMappingsRetriever.class),
+ Arguments.of("other statement context",
createOtherSqlStatementContext(), null));
+ }
+
+ private static SQLStatementContext createInsertSqlStatementContext() {
+ InsertStatementContext result = mock(InsertStatementContext.class,
RETURNS_DEEP_STUBS);
+ when(result.getSqlStatement()).thenReturn(mock(InsertStatement.class));
+
when(result.getTablesContext().getDatabaseName()).thenReturn(Optional.empty());
+
when(result.getTablesContext().getTableNames()).thenReturn(Collections.singleton("t_order"));
+ return result;
+ }
+
+ private static SQLStatementContext createDeleteSqlStatementContext() {
+ DeleteStatementContext result = mock(DeleteStatementContext.class,
RETURNS_DEEP_STUBS);
+ when(result.getSqlStatement()).thenReturn(mock(DeleteStatement.class));
+
when(result.getTablesContext().getDatabaseName()).thenReturn(Optional.empty());
+
when(result.getTablesContext().getTableNames()).thenReturn(Collections.singleton("t_order"));
+ return result;
+ }
+
+ private static SQLStatementContext createUpdateSqlStatementContext() {
+ UpdateStatementContext result = mock(UpdateStatementContext.class,
RETURNS_DEEP_STUBS);
+ when(result.getSqlStatement()).thenReturn(mock(UpdateStatement.class));
+
when(result.getTablesContext().getDatabaseName()).thenReturn(Optional.empty());
+
when(result.getTablesContext().getTableNames()).thenReturn(Collections.singleton("t_order"));
+ return result;
+ }
+
+ private static SQLStatementContext createSelectSqlStatementContext() {
+ SelectStatementContext result = mock(SelectStatementContext.class,
RETURNS_DEEP_STUBS);
+ when(result.getSqlStatement()).thenReturn(mock(SelectStatement.class));
+
when(result.getTablesContext().getDatabaseName()).thenReturn(Optional.empty());
+
when(result.getTablesContext().getTableNames()).thenReturn(Collections.singleton("t_order"));
+ return result;
+ }
+
+ private static SQLStatementContext createOtherSqlStatementContext() {
+ SQLStatementContext result = mock(SQLStatementContext.class,
RETURNS_DEEP_STUBS);
+ when(result.getSqlStatement()).thenReturn(mock(SQLStatement.class));
+
when(result.getTablesContext().getDatabaseName()).thenReturn(Optional.empty());
+
when(result.getTablesContext().getTableNames()).thenReturn(Collections.singleton("t_order"));
+ return result;
+ }
+
+ private static Stream<Arguments> retrieveArguments() {
+ return Stream.of(
+ Arguments.of("return table hint result when it is not empty",
Collections.singletonMap("foo_prod_ds", "foo_shadow_ds"),
+ Collections.singletonMap("foo_prod_ds",
"bar_shadow_ds"), true, Collections.singletonMap("foo_prod_ds",
"foo_shadow_ds")),
+ Arguments.of("return column retriever result when table hint
result is empty",
+ Collections.emptyMap(),
Collections.singletonMap("foo_prod_ds", "foo_shadow_ds"), true,
Collections.singletonMap("foo_prod_ds", "foo_shadow_ds")),
+ Arguments.of("return table hint result when table hint result
is empty and shadow column retriever is null",
+ Collections.emptyMap(), Collections.emptyMap(), false,
Collections.emptyMap()));
+ }
+}