This is an automated email from the ASF dual-hosted git repository.
terrymanu 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 4cb41851215 Fix CREATE READWRITE_SPLITTING RULE IF NOT EXISTS for
existing rule (#39371)
4cb41851215 is described below
commit 4cb41851215ec88693f1e104a453aef6621803cd
Author: Eunbin Son <[email protected]>
AuthorDate: Tue Sep 8 19:19:28 2026 +0900
Fix CREATE READWRITE_SPLITTING RULE IF NOT EXISTS for existing rule (#39371)
* Fix CREATE READWRITE_SPLITTING RULE IF NOT EXISTS for existing rule
* Skip existing rules in readwrite-splitting IF NOT EXISTS checks
checkCreation skipped only the duplicate-name check for IF NOT EXISTS and
continued into actual data source validation with every submitted
segment. Since that validation seeds the duplicate sets from the whole
current configuration, repeating an unchanged rule threw
DuplicateReadwriteSplittingActualDataSourceException instead of becoming
a no-op, because the executor removes existing-name segments later.
Exclude segments whose rule name already exists from the remaining
creation validations when IF NOT EXISTS is specified, and keep validating
genuinely new segments.
Add a case reusing the same rule name and the same write and read storage
units, and a case where a new segment reuses an existing write data
source and must still fail.
* Validate new readwrite-splitting names against expanded names
Excluding every ReadwriteSplittingRule mapper key from the logic data
source set also removed collision protection: with an existing group
rw_${0..1}, creating literal rw_0 passed because the remaining duplicate
checks compare raw configuration names, and rebuilding the rule then
merged both definitions under expanded key rw_0.
Restore the full logic data source set and instead exempt only the exact
existing configurations that IF NOT EXISTS discards, so genuinely new
segments are still validated against all expanded logical names.
---------
Co-authored-by: Liang Zhang <[email protected]>
---
RELEASE-NOTES.md | 1 +
.../ReadwriteSplittingRuleStatementChecker.java | 20 ++++--
...ReadwriteSplittingRuleStatementCheckerTest.java | 74 +++++++++++++++++++++-
3 files changed, 87 insertions(+), 8 deletions(-)
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 7b429a9443d..628090686a6 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -42,6 +42,7 @@
1. Metadata: Fix wrong logic table metadata when config same actual table name
in different storage unit -
[#39157](https://github.com/apache/shardingsphere/pull/39157)
1. DistSQL: Fix case-sensitive storage unit matching in `SHOW RULES USED
STORAGE UNIT` - [#38848](https://github.com/apache/shardingsphere/pull/38848)
1. DistSQL: Use case-insensitive table name matching in broadcast create and
drop executors - [#39200](https://github.com/apache/shardingsphere/pull/39200)
+1. DistSQL: Fix `CREATE READWRITE_SPLITTING RULE IF NOT EXISTS` failing for an
existing rule name -
[#39371](https://github.com/apache/shardingsphere/pull/39371)
1. DistSQL: Match mask table names case-insensitively in create and alter
executors - [#39361](https://github.com/apache/shardingsphere/pull/39361)
1. JDBC: Fix stale generated values leaking into prepared statement
executeBatch calls without pending batches -
[#38160](https://github.com/apache/shardingsphere/pull/38160)
1. JDBC: Fix MySQL-compatible typed string conversion for
`ResultSet#getObject(index, Class<T>)` -
[#38444](https://github.com/apache/shardingsphere/pull/38444)
diff --git
a/features/readwrite-splitting/distsql/handler/src/main/java/org/apache/shardingsphere/readwritesplitting/distsql/handler/checker/ReadwriteSplittingRuleStatementChecker.java
b/features/readwrite-splitting/distsql/handler/src/main/java/org/apache/shardingsphere/readwritesplitting/distsql/handler/checker/ReadwriteSplittingRuleStatementChecker.java
index 414b1eed691..0e77b52a023 100644
---
a/features/readwrite-splitting/distsql/handler/src/main/java/org/apache/shardingsphere/readwritesplitting/distsql/handler/checker/ReadwriteSplittingRuleStatementChecker.java
+++
b/features/readwrite-splitting/distsql/handler/src/main/java/org/apache/shardingsphere/readwritesplitting/distsql/handler/checker/ReadwriteSplittingRuleStatementChecker.java
@@ -67,10 +67,20 @@ public final class ReadwriteSplittingRuleStatementChecker {
final ReadwriteSplittingRuleConfiguration
currentRuleConfig, final boolean ifNotExists) {
checkDuplicateRuleNames(database, segments, currentRuleConfig,
ifNotExists);
String databaseName = database.getName();
- checkDataSourcesExist(databaseName, segments, database);
- checkDuplicatedDataSourceNames(databaseName, segments,
currentRuleConfig, true);
- checkTransactionalReadQueryStrategy(segments);
- checkLoadBalancers(segments);
+ Collection<ReadwriteSplittingRuleSegment> toBeCreatedSegments =
ifNotExists ? getNotExistedSegments(segments, currentRuleConfig) : segments;
+ checkDataSourcesExist(databaseName, toBeCreatedSegments, database);
+ checkDuplicatedDataSourceNames(databaseName, toBeCreatedSegments,
currentRuleConfig, true);
+ checkTransactionalReadQueryStrategy(toBeCreatedSegments);
+ checkLoadBalancers(toBeCreatedSegments);
+ }
+
+ private static Collection<ReadwriteSplittingRuleSegment>
getNotExistedSegments(final Collection<ReadwriteSplittingRuleSegment> segments,
+
final ReadwriteSplittingRuleConfiguration currentRuleConfig) {
+ if (null == currentRuleConfig) {
+ return segments;
+ }
+ Collection<String> currentRuleNames =
currentRuleConfig.getDataSourceGroups().stream().map(ReadwriteSplittingDataSourceGroupRuleConfiguration::getName).collect(Collectors.toSet());
+ return segments.stream().filter(each ->
!currentRuleNames.contains(each.getName())).collect(Collectors.toList());
}
/**
@@ -100,7 +110,7 @@ public final class ReadwriteSplittingRuleStatementChecker {
private static void checkDuplicateRuleNames(final ShardingSphereDatabase
database,
final
Collection<ReadwriteSplittingRuleSegment> segments, final
ReadwriteSplittingRuleConfiguration currentRuleConfig, final boolean
ifNotExists) {
checkDuplicateRuleNamesWithSelf(database.getName(), segments);
- checkDuplicateRuleNamesWithExistsDataSources(database, segments);
+ checkDuplicateRuleNamesWithExistsDataSources(database, ifNotExists ?
getNotExistedSegments(segments, currentRuleConfig) : segments);
if (!ifNotExists) {
checkDuplicateRuleNamesWithRuleConfiguration(database.getName(),
currentRuleConfig, segments);
}
diff --git
a/features/readwrite-splitting/distsql/handler/src/test/java/org/apache/shardingsphere/readwritesplitting/distsql/handler/checker/ReadwriteSplittingRuleStatementCheckerTest.java
b/features/readwrite-splitting/distsql/handler/src/test/java/org/apache/shardingsphere/readwritesplitting/distsql/handler/checker/ReadwriteSplittingRuleStatementCheckerTest.java
index d148530299b..5f7d9bedd3c 100644
---
a/features/readwrite-splitting/distsql/handler/src/test/java/org/apache/shardingsphere/readwritesplitting/distsql/handler/checker/ReadwriteSplittingRuleStatementCheckerTest.java
+++
b/features/readwrite-splitting/distsql/handler/src/test/java/org/apache/shardingsphere/readwritesplitting/distsql/handler/checker/ReadwriteSplittingRuleStatementCheckerTest.java
@@ -26,6 +26,9 @@ import
org.apache.shardingsphere.infra.exception.kernel.metadata.rule.MissingReq
import
org.apache.shardingsphere.infra.exception.kernel.metadata.rule.MissingRequiredStrategyException;
import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import
org.apache.shardingsphere.infra.metadata.database.resource.ResourceMetaData;
+import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
+import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
+import org.apache.shardingsphere.infra.rule.attribute.RuleAttributes;
import
org.apache.shardingsphere.infra.rule.attribute.datasource.DataSourceMapperRuleAttribute;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.infra.util.props.PropertiesBuilder;
@@ -34,6 +37,7 @@ import
org.apache.shardingsphere.readwritesplitting.config.ReadwriteSplittingRul
import
org.apache.shardingsphere.readwritesplitting.config.rule.ReadwriteSplittingDataSourceGroupRuleConfiguration;
import
org.apache.shardingsphere.readwritesplitting.distsql.segment.ReadwriteSplittingRuleSegment;
import
org.apache.shardingsphere.readwritesplitting.exception.actual.DuplicateReadwriteSplittingActualDataSourceException;
+import
org.apache.shardingsphere.readwritesplitting.rule.ReadwriteSplittingRule;
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;
@@ -48,6 +52,8 @@ import org.mockito.Mock;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.Properties;
import java.util.stream.Stream;
@@ -55,6 +61,7 @@ import static
org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(AutoMockExtension.class)
@@ -70,7 +77,7 @@ class ReadwriteSplittingRuleStatementCheckerTest {
@BeforeEach
void setUp() {
when(database.getName()).thenReturn("foo_db");
-
when(database.getRuleMetaData().getAttributes(DataSourceMapperRuleAttribute.class)).thenReturn(Collections.emptyList());
+ when(database.getRuleMetaData()).thenReturn(new
RuleMetaData(Collections.emptyList()));
lenient().when(resourceMetaData.getStorageUnits()).thenReturn(Collections.emptyMap());
lenient().when(resourceMetaData.getNotExistedDataSources(any())).thenReturn(Collections.emptySet());
when(database.getResourceMetaData()).thenReturn(resourceMetaData);
@@ -130,10 +137,53 @@ class ReadwriteSplittingRuleStatementCheckerTest {
assertDoesNotThrow(() ->
ReadwriteSplittingRuleStatementChecker.checkCreation(database,
Collections.singleton(segment), currentRuleConfig, true));
}
+ @Test
+ void assertCheckCreationWithIfNotExistsWhenActualDataSourcesUnchanged() {
+ ReadwriteSplittingRuleSegment segment = new
ReadwriteSplittingRuleSegment("foo_rule_0", "write_ds_0",
Arrays.asList("read_ds_0", "read_ds_1"), null, null);
+ Collection<ReadwriteSplittingDataSourceGroupRuleConfiguration>
dataSourceGroups = Collections.singleton(
+ new
ReadwriteSplittingDataSourceGroupRuleConfiguration("foo_rule_0", "write_ds_0",
Arrays.asList("read_ds_0", "read_ds_1"), "RANDOM"));
+ ReadwriteSplittingRuleConfiguration currentRuleConfig = new
ReadwriteSplittingRuleConfiguration(dataSourceGroups, Collections.emptyMap());
+ assertDoesNotThrow(() ->
ReadwriteSplittingRuleStatementChecker.checkCreation(database,
Collections.singleton(segment), currentRuleConfig, true));
+ }
+
+ @Test
+ void
assertCheckCreationWithIfNotExistsWhenNewSegmentDuplicatesExistedDataSource() {
+ ReadwriteSplittingRuleSegment existedSegment = new
ReadwriteSplittingRuleSegment("foo_rule_0", "write_ds_0",
Arrays.asList("read_ds_0", "read_ds_1"), null, null);
+ ReadwriteSplittingRuleSegment newSegment = new
ReadwriteSplittingRuleSegment("foo_rule_1", "write_ds_0",
Collections.singletonList("read_ds_2"), null, null);
+ Collection<ReadwriteSplittingDataSourceGroupRuleConfiguration>
dataSourceGroups = Collections.singleton(
+ new
ReadwriteSplittingDataSourceGroupRuleConfiguration("foo_rule_0", "write_ds_0",
Arrays.asList("read_ds_0", "read_ds_1"), "RANDOM"));
+ ReadwriteSplittingRuleConfiguration currentRuleConfig = new
ReadwriteSplittingRuleConfiguration(dataSourceGroups, Collections.emptyMap());
+
assertThrows(DuplicateReadwriteSplittingActualDataSourceException.class,
+ () ->
ReadwriteSplittingRuleStatementChecker.checkCreation(database,
Arrays.asList(existedSegment, newSegment), currentRuleConfig, true));
+ }
+
+ @Test
+ void
assertCheckCreationWithIfNotExistsWhenRuleNameExistsInExpandedLogicDataSources()
{
+ RuleMetaData ruleMetaData =
mockRuleMetaDataWithLogicDataSources(ReadwriteSplittingRule.class,
Arrays.asList("rw_0", "rw_1"));
+ when(database.getRuleMetaData()).thenReturn(ruleMetaData);
+ ReadwriteSplittingRuleSegment segment = new
ReadwriteSplittingRuleSegment("rw_0", "write_ds_0",
Collections.singletonList("read_ds_0"), null, null);
+ Collection<ReadwriteSplittingDataSourceGroupRuleConfiguration>
dataSourceGroups = Collections.singleton(
+ new
ReadwriteSplittingDataSourceGroupRuleConfiguration("rw_${0..1}", "write_ds_9",
Collections.singletonList("read_ds_9"), "RANDOM"));
+ ReadwriteSplittingRuleConfiguration currentRuleConfig = new
ReadwriteSplittingRuleConfiguration(dataSourceGroups, Collections.emptyMap());
+ assertThrows(InvalidRuleConfigurationException.class,
+ () ->
ReadwriteSplittingRuleStatementChecker.checkCreation(database,
Collections.singleton(segment), currentRuleConfig, true));
+ }
+
+ @Test
+ void
assertCheckCreationWithIfNotExistsWhenRuleNameExistsInOwnLogicDataSources() {
+ RuleMetaData ruleMetaData =
mockRuleMetaDataWithLogicDataSource(ReadwriteSplittingRule.class, "foo_rule_0");
+ when(database.getRuleMetaData()).thenReturn(ruleMetaData);
+ ReadwriteSplittingRuleSegment segment = new
ReadwriteSplittingRuleSegment("foo_rule_0", "write_ds_0",
Arrays.asList("read_ds_0", "read_ds_1"), null, null);
+ Collection<ReadwriteSplittingDataSourceGroupRuleConfiguration>
dataSourceGroups = Collections.singleton(
+ new
ReadwriteSplittingDataSourceGroupRuleConfiguration("foo_rule_0", "write_ds_9",
Collections.singletonList("read_ds_9"), "RANDOM"));
+ ReadwriteSplittingRuleConfiguration currentRuleConfig = new
ReadwriteSplittingRuleConfiguration(dataSourceGroups, Collections.emptyMap());
+ assertDoesNotThrow(() ->
ReadwriteSplittingRuleStatementChecker.checkCreation(database,
Collections.singleton(segment), currentRuleConfig, true));
+ }
+
@Test
void assertCheckCreationWithDuplicateRuleNameInLogicDataSources() {
- DataSourceMapperRuleAttribute ruleAttribute = () ->
Collections.singletonMap("foo_rule_0", Collections.singleton("actual_ds_0"));
-
when(database.getRuleMetaData().getAttributes(DataSourceMapperRuleAttribute.class)).thenReturn(Collections.singleton(ruleAttribute));
+ RuleMetaData ruleMetaData =
mockRuleMetaDataWithLogicDataSource(ShardingSphereRule.class, "foo_rule_0");
+ when(database.getRuleMetaData()).thenReturn(ruleMetaData);
ReadwriteSplittingRuleSegment segment = new
ReadwriteSplittingRuleSegment("foo_rule_0", "write_ds_0",
Arrays.asList("read_ds_0", "read_ds_1"), null, null);
assertThrows(InvalidRuleConfigurationException.class, () ->
ReadwriteSplittingRuleStatementChecker.checkCreation(database,
Collections.singleton(segment), null, false));
}
@@ -186,6 +236,24 @@ class ReadwriteSplittingRuleStatementCheckerTest {
assertThrows(MissingRequiredRuleException.class, () ->
ReadwriteSplittingRuleStatementChecker.checkAlteration(database,
Collections.singleton(segment), currentRuleConfig));
}
+ private RuleMetaData mockRuleMetaDataWithLogicDataSources(final Class<?
extends ShardingSphereRule> ruleClass, final Collection<String>
logicDataSourceNames) {
+ ShardingSphereRule rule = mock(ruleClass);
+ Map<String, Collection<String>> dataSourceMapper = new
LinkedHashMap<>(logicDataSourceNames.size(), 1F);
+ for (String each : logicDataSourceNames) {
+ dataSourceMapper.put(each, Collections.singleton("actual_ds_0"));
+ }
+ DataSourceMapperRuleAttribute ruleAttribute = () -> dataSourceMapper;
+ lenient().when(rule.getAttributes()).thenReturn(new
RuleAttributes(ruleAttribute));
+ return new RuleMetaData(Collections.singleton(rule));
+ }
+
+ private RuleMetaData mockRuleMetaDataWithLogicDataSource(final Class<?
extends ShardingSphereRule> ruleClass, final String logicDataSourceName) {
+ ShardingSphereRule rule = mock(ruleClass);
+ DataSourceMapperRuleAttribute ruleAttribute = () ->
Collections.singletonMap(logicDataSourceName,
Collections.singleton("actual_ds_0"));
+ lenient().when(rule.getAttributes()).thenReturn(new
RuleAttributes(ruleAttribute));
+ return new RuleMetaData(Collections.singleton(rule));
+ }
+
private static Stream<Arguments> invalidWeightConfigurationArguments() {
return Stream.of(
Arguments.of("empty weight properties",