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 4b8fc310821 Add more test cases on SingleSQLFederationDeciderTest
(#38081)
4b8fc310821 is described below
commit 4b8fc310821a31a9b88d9fc72f789ea04d3ec858
Author: Liang Zhang <[email protected]>
AuthorDate: Wed Feb 18 17:00:03 2026 +0800
Add more test cases on SingleSQLFederationDeciderTest (#38081)
---
.codex/skills/gen-ut/SKILL.md | 34 +++++
.../single/decider/SingleSQLFederationDecider.java | 3 -
.../decider/SingleSQLFederationDeciderTest.java | 140 ++++++++++++++-------
3 files changed, 130 insertions(+), 47 deletions(-)
diff --git a/.codex/skills/gen-ut/SKILL.md b/.codex/skills/gen-ut/SKILL.md
index 444426968f3..656cadc9790 100644
--- a/.codex/skills/gen-ut/SKILL.md
+++ b/.codex/skills/gen-ut/SKILL.md
@@ -53,6 +53,7 @@ Module resolution order:
- `R2`: test types and naming
- Non-parameterized scenarios `MUST` use JUnit `@Test`.
- Data-driven scenarios `MUST` use JUnit `@ParameterizedTest(name = "{0}")`
with `@MethodSource` + `Arguments`.
+ - Parameterized test method signatures `MUST` use `final String name` as the
first parameter.
- Each parameterized test `MUST` provide at least 3 `Arguments` rows; fewer
than 3 is a violation and `MUST` be converted to non-parameterized `@Test`.
- `MUST NOT` use `@RepeatedTest`.
- Test method naming `MUST` follow `CODE_OF_CONDUCT.md`: use the `assert`
prefix; when a single test uniquely covers a production method, use
`assert<MethodName>`.
@@ -161,6 +162,7 @@ Module resolution order:
- `R15-B` (metadata accessor test ban): unless the user explicitly requests
it in the current turn, tests targeting `getType` / `getOrder` / `getTypeClass`
`MUST NOT` be added.
- `R15-C` (scope mutation guard): test-generation tasks `MUST NOT` introduce
new diffs under any `src/main/` path.
- `R15-D` (parameterized argument floor): each `@ParameterizedTest` `MUST`
bind to `@MethodSource` providers that together contain at least 3 `Arguments`
rows; otherwise it is a violation.
+ - `R15-E` (parameterized name parameter): each `@ParameterizedTest` method
`MUST` declare the first parameter exactly as `final String name`.
## Workflow
@@ -446,6 +448,38 @@ PY
'
```
+5.3 `R15-E` parameterized first-parameter scan:
+```bash
+bash -lc '
+python3 - <ResolvedTestFileSet> <<'"'"'PY'"'"'
+import re
+import sys
+from pathlib import Path
+
+PARAM_METHOD_PATTERN =
re.compile(r"@ParameterizedTest(?:\\s*\\([^)]*\\))?\\s*(?:@\\w+(?:\\s*\\([^)]*\\))?\\s*)*void\\s+(assert\\w+)\\s*\\(([^)]*)\\)",
re.S)
+violations = []
+for path in (each for each in sys.argv[1:] if each.endswith(".java")):
+ source = Path(path).read_text(encoding="utf-8")
+ for match in PARAM_METHOD_PATTERN.finditer(source):
+ method_name = match.group(1)
+ params = match.group(2).strip()
+ line = source.count("\\n", 0, match.start()) + 1
+ if not params:
+ violations.append(f"{path}:{line} method={method_name}
missingParameters")
+ continue
+ first_param = params.split(",", 1)[0].strip()
+ normalized = re.sub(r"\\s+", " ", first_param)
+ if "final String name" != normalized:
+ violations.append(f"{path}:{line} method={method_name}
firstParam={first_param}")
+if violations:
+ print("[R15-E] each @ParameterizedTest method must declare first parameter
as `final String name`")
+ for each in violations:
+ print(each)
+ sys.exit(1)
+PY
+'
+```
+
6. `R14` hard-gate scan:
```bash
bash -lc '
diff --git
a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/decider/SingleSQLFederationDecider.java
b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/decider/SingleSQLFederationDecider.java
index a578d49905d..2c31e0fba5d 100644
---
a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/decider/SingleSQLFederationDecider.java
+++
b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/decider/SingleSQLFederationDecider.java
@@ -72,9 +72,6 @@ public final class SingleSQLFederationDecider implements
SQLFederationDecider<Si
}
private boolean isInnerCommaJoin(final SQLStatement sqlStatement) {
- if (!(sqlStatement instanceof SelectStatement)) {
- return true;
- }
SelectStatement selectStatement = (SelectStatement) sqlStatement;
if (!selectStatement.getFrom().isPresent() ||
!(selectStatement.getFrom().get() instanceof JoinTableSegment)) {
return true;
diff --git
a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/decider/SingleSQLFederationDeciderTest.java
b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/decider/SingleSQLFederationDeciderTest.java
index 5ad572966ef..0afe73e4ff3 100644
---
a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/decider/SingleSQLFederationDeciderTest.java
+++
b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/decider/SingleSQLFederationDeciderTest.java
@@ -18,26 +18,39 @@
package org.apache.shardingsphere.single.decider;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import
org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
+import
org.apache.shardingsphere.infra.binder.context.statement.type.dal.ExplainStatementContext;
import
org.apache.shardingsphere.infra.binder.context.statement.type.dml.SelectStatementContext;
import org.apache.shardingsphere.infra.datanode.DataNode;
+import
org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException;
import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
import org.apache.shardingsphere.infra.metadata.database.schema.QualifiedTable;
import org.apache.shardingsphere.infra.rule.attribute.RuleAttributes;
import
org.apache.shardingsphere.infra.rule.attribute.datanode.MutableDataNodeRuleAttribute;
+import org.apache.shardingsphere.infra.spi.type.ordered.OrderedSPILoader;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.single.rule.SingleRule;
+import org.apache.shardingsphere.sql.parser.statement.core.enums.JoinType;
+import
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.JoinTableSegment;
+import
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.TableSegment;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
+import org.apache.shardingsphere.sqlfederation.spi.SQLFederationDecider;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
+import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -46,77 +59,116 @@ import static org.mockito.Mockito.when;
class SingleSQLFederationDeciderTest {
+ private static final DatabaseType DATABASE_TYPE =
TypedSPILoader.getService(DatabaseType.class, "FIXTURE");
+
+ private final SingleSQLFederationDecider decider =
(SingleSQLFederationDecider) OrderedSPILoader.getServicesByClass(
+ SQLFederationDecider.class,
Collections.singleton(SingleRule.class)).get(SingleRule.class);
+
@Test
- void assertDecideWhenNotContainsSingleTable() {
- SelectStatementContext select = mockStatementContext();
+ void assertDecideWhenSingleTablesContainView() {
+ Collection<QualifiedTable> qualifiedTables =
Collections.singletonList(new QualifiedTable("foo_db", "foo_tbl"));
+ SingleRule rule = createSingleRule(qualifiedTables, true);
+ SelectStatementContext selectStatementContext =
createSelectStatementContext(new SelectStatement(DATABASE_TYPE));
Collection<DataNode> includedDataNodes = new HashSet<>();
- assertFalse(new SingleSQLFederationDecider().decide(select,
Collections.emptyList(), mock(RuleMetaData.class), mockDatabase(),
mock(SingleRule.class), includedDataNodes));
+ assertTrue(decider.decide(selectStatementContext,
Collections.emptyList(), mock(RuleMetaData.class), createDatabase(true), rule,
includedDataNodes));
assertTrue(includedDataNodes.isEmpty());
}
- @Test
- void assertDecideWhenAllSingleTablesInSameComputeNode() {
- Collection<QualifiedTable> qualifiedTables = Arrays.asList(new
QualifiedTable("foo_db", "t_order"), new QualifiedTable("foo_db",
"t_order_item"));
- SingleRule rule = createSingleRule(qualifiedTables);
- SelectStatementContext select = mockStatementContext();
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("assertDecideWithComputeNodeResultArguments")
+ void assertDecideWithComputeNodeResult(final String name, final boolean
allTablesInSameComputeNode, final boolean hasOrderDataNode, final boolean
expectedDecideResult,
+ final int
expectedIncludedDataNodeSize) {
+ Collection<QualifiedTable> qualifiedTables =
Collections.singletonList(new QualifiedTable("foo_db", "foo_tbl"));
+ SelectStatementContext selectStatementContext =
createSelectStatementContext(new SelectStatement(DATABASE_TYPE));
+ SingleRule rule = createSingleRule(qualifiedTables, hasOrderDataNode);
+ when(rule.isAllTablesInSameComputeNode(any(),
any())).thenReturn(allTablesInSameComputeNode);
Collection<DataNode> includedDataNodes = new HashSet<>();
- when(rule.isAllTablesInSameComputeNode(includedDataNodes,
qualifiedTables)).thenReturn(true);
- assertFalse(new SingleSQLFederationDecider().decide(select,
Collections.emptyList(), mock(RuleMetaData.class), mockDatabase(), rule,
includedDataNodes));
- assertThat(includedDataNodes.size(), is(2));
+ assertThat(name, decider.decide(selectStatementContext,
Collections.emptyList(), mock(RuleMetaData.class), createDatabase(false), rule,
includedDataNodes), is(expectedDecideResult));
+ assertThat(includedDataNodes.size(), is(expectedIncludedDataNodeSize));
}
- @Test
- void assertDecideWhenAllSingleTablesNotInSameComputeNode() {
- Collection<QualifiedTable> qualifiedTables = Arrays.asList(new
QualifiedTable("foo_db", "t_order"), new QualifiedTable("foo_db",
"t_order_item"));
- SingleRule rule = createSingleRule(qualifiedTables);
- SelectStatementContext select = mockStatementContext();
- Collection<DataNode> includedDataNodes = new HashSet<>();
- when(rule.isAllTablesInSameComputeNode(includedDataNodes,
qualifiedTables)).thenReturn(false);
- assertTrue(new SingleSQLFederationDecider().decide(select,
Collections.emptyList(), mock(RuleMetaData.class), mockDatabase(), rule,
includedDataNodes));
- assertThat(includedDataNodes.size(), is(2));
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("assertDecideWithIncludedDataNodesAndJoinTypeArguments")
+ void assertDecideWithIncludedDataNodesAndJoinType(final String name, final
SelectStatement sqlStatement, final boolean expectedDecideResult, final int
expectedIncludedDataNodeSize) {
+ Collection<QualifiedTable> qualifiedTables =
Collections.singletonList(new QualifiedTable("foo_db", "foo_tbl"));
+ SingleRule rule = createSingleRule(qualifiedTables, true);
+ when(rule.isAllTablesInSameComputeNode(any(), any())).thenReturn(true);
+ SelectStatementContext selectStatementContext =
createSelectStatementContext(sqlStatement);
+ Collection<DataNode> includedDataNodes = new
HashSet<>(Collections.singleton(new DataNode("ds_1", (String) null, "t_user")));
+ assertThat(name, decider.decide(selectStatementContext,
Collections.emptyList(), mock(RuleMetaData.class), createDatabase(false), rule,
includedDataNodes), is(expectedDecideResult));
+ assertThat(includedDataNodes.size(), is(expectedIncludedDataNodeSize));
}
@Test
- void assertDecideWhenAllTablesInSameComputeNode() {
- Collection<QualifiedTable> qualifiedTables = Arrays.asList(new
QualifiedTable("foo_db", "t_order"), new QualifiedTable("foo_db",
"t_order_item"));
- SingleRule rule = createSingleRule(qualifiedTables);
- SelectStatementContext select = mockStatementContext();
- Collection<DataNode> includedDataNodes = new
HashSet<>(Collections.singleton(new DataNode("ds_0", (String) null, "t_user")));
- when(rule.isAllTablesInSameComputeNode(includedDataNodes,
qualifiedTables)).thenReturn(true);
- assertFalse(new SingleSQLFederationDecider().decide(select,
Collections.emptyList(), mock(RuleMetaData.class), mockDatabase(), rule,
includedDataNodes));
- assertThat(includedDataNodes.size(), is(3));
+ void assertDecideWhenExplainStatementContext() {
+ ExplainStatementContext explainStatementContext =
mock(ExplainStatementContext.class);
+ SelectStatementContext explainableSQLStatementContext =
createSelectStatementContext(new SelectStatement(DATABASE_TYPE));
+
when(explainStatementContext.getExplainableSQLStatementContext()).thenReturn(explainableSQLStatementContext);
+ Collection<DataNode> includedDataNodes = new HashSet<>();
+ SingleRule rule = createSingleRule(Collections.emptyList(), true);
+ assertFalse(decider.decide(explainStatementContext,
Collections.emptyList(), mock(RuleMetaData.class), createDatabase(false), rule,
includedDataNodes));
+ assertTrue(includedDataNodes.isEmpty());
}
@Test
- void assertDecideWhenAllTablesNotInSameComputeNode() {
- Collection<QualifiedTable> qualifiedTables = Arrays.asList(new
QualifiedTable("foo_db", "t_order"), new QualifiedTable("foo_db",
"t_order_item"));
- SingleRule rule = createSingleRule(qualifiedTables);
- SelectStatementContext select = mockStatementContext();
- Collection<DataNode> includedDataNodes = new
HashSet<>(Collections.singleton(new DataNode("ds_1", (String) null, "t_user")));
- when(rule.isAllTablesInSameComputeNode(includedDataNodes,
qualifiedTables)).thenReturn(false);
- assertTrue(new SingleSQLFederationDecider().decide(select,
Collections.emptyList(), mock(RuleMetaData.class), mockDatabase(), rule,
includedDataNodes));
- assertThat(includedDataNodes.size(), is(3));
+ void assertDecideWhenSQLStatementContextNotSupported() {
+ SQLStatementContext sqlStatementContext =
mock(SQLStatementContext.class);
+ when(sqlStatementContext.getSqlStatement()).thenReturn(new
SelectStatement(DATABASE_TYPE));
+ UnsupportedSQLOperationException actual =
assertThrows(UnsupportedSQLOperationException.class,
+ () -> decider.decide(sqlStatementContext,
Collections.emptyList(), mock(RuleMetaData.class), createDatabase(false),
mock(SingleRule.class), new HashSet<>()));
+ assertThat(actual.getMessage(), is("Unsupported SQL operation:
unsupported SQL statement SelectStatement in sql federation."));
}
- private SingleRule createSingleRule(final Collection<QualifiedTable>
qualifiedTables) {
+ private SingleRule createSingleRule(final Collection<QualifiedTable>
qualifiedTables, final boolean hasOrderDataNode) {
SingleRule result = mock(SingleRule.class);
when(result.getSingleTables(any())).thenReturn(qualifiedTables);
+ when(result.getQualifiedTables(any(),
any())).thenReturn(qualifiedTables);
MutableDataNodeRuleAttribute ruleAttribute =
mock(MutableDataNodeRuleAttribute.class);
- when(ruleAttribute.findTableDataNode("foo_db",
"t_order")).thenReturn(Optional.of(new DataNode("ds_0", (String) null,
"t_order")));
- when(ruleAttribute.findTableDataNode("foo_db",
"t_order_item")).thenReturn(Optional.of(new DataNode("ds_0", (String) null,
"t_order_item")));
+ when(ruleAttribute.findTableDataNode("foo_db",
"foo_tbl")).thenReturn(hasOrderDataNode ? Optional.of(new DataNode("ds_0",
(String) null, "foo_tbl")) : Optional.empty());
when(result.getAttributes()).thenReturn(new
RuleAttributes(ruleAttribute));
return result;
}
- private SelectStatementContext mockStatementContext() {
- SelectStatementContext result = mock(SelectStatementContext.class,
RETURNS_DEEP_STUBS);
-
when(result.getSqlStatement().getDatabaseType()).thenReturn(TypedSPILoader.getService(DatabaseType.class,
"FIXTURE"));
+ private SelectStatementContext createSelectStatementContext(final
SelectStatement sqlStatement) {
+ SelectStatementContext result = mock(SelectStatementContext.class);
+ when(result.getSqlStatement()).thenReturn(sqlStatement);
return result;
}
- private ShardingSphereDatabase mockDatabase() {
+ private ShardingSphereDatabase createDatabase(final boolean containsView) {
ShardingSphereDatabase result = mock(ShardingSphereDatabase.class,
RETURNS_DEEP_STUBS);
when(result.getName()).thenReturn("foo_db");
+
when(result.getSchema("foo_db").containsView("foo_tbl")).thenReturn(containsView);
+ return result;
+ }
+
+ private static Stream<Arguments>
assertDecideWithComputeNodeResultArguments() {
+ return Stream.of(
+ Arguments.of("all tables in same compute node with table data
node", true, true, false, 1),
+ Arguments.of("all tables not in same compute node with table
data node", false, true, true, 1),
+ Arguments.of("all tables in same compute node without table
data node", true, false, false, 0));
+ }
+
+ private static Stream<Arguments>
assertDecideWithIncludedDataNodesAndJoinTypeArguments() {
+ return Stream.of(
+ Arguments.of("select statement without from segment", new
SelectStatement(DATABASE_TYPE), false, 2),
+ Arguments.of("select statement with non join from segment",
createSelectStatementWithNonJoinFrom(), false, 2),
+ Arguments.of("select statement with inner join",
createSelectStatementWithJoinType(JoinType.INNER.name()), false, 2),
+ Arguments.of("select statement with comma join",
createSelectStatementWithJoinType(JoinType.COMMA.name()), false, 2),
+ Arguments.of("select statement with left join",
createSelectStatementWithJoinType(JoinType.LEFT.name()), true, 1));
+ }
+
+ private static SelectStatement createSelectStatementWithNonJoinFrom() {
+ SelectStatement result = new SelectStatement(DATABASE_TYPE);
+ result.setFrom(mock(TableSegment.class));
+ return result;
+ }
+
+ private static SelectStatement createSelectStatementWithJoinType(final
String joinType) {
+ SelectStatement result = new SelectStatement(DATABASE_TYPE);
+ JoinTableSegment joinTableSegment = new JoinTableSegment();
+ joinTableSegment.setJoinType(joinType);
+ result.setFrom(joinTableSegment);
return result;
}
}