This is an automated email from the ASF dual-hosted git repository.

menghaoranss 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 4ff4642c9aa Implement mutable data node rule attribute with 
copy-on-write support (#39355)
4ff4642c9aa is described below

commit 4ff4642c9aa9734a04c631bf11741e6a93eb91ec
Author: Haoran Meng <[email protected]>
AuthorDate: Thu Aug 6 10:39:52 2026 +0800

    Implement mutable data node rule attribute with copy-on-write support 
(#39355)
---
 .../metadata/database/ShardingSphereDatabase.java  | 38 +++++++++-
 .../infra/metadata/database/rule/RuleMetaData.java | 46 ++++++++++++
 .../datanode/MutableDataNodeRuleAttribute.java     | 19 +++++
 .../database/ShardingSphereDatabaseTest.java       | 46 ++++++++++++
 .../metadata/database/rule/RuleMetaDataTest.java   | 51 +++++++++++++
 .../shardingsphere/single/rule/SingleRule.java     | 48 +++++++++++-
 .../SingleMutableDataNodeRuleAttribute.java        | 12 +++
 .../shardingsphere/single/rule/SingleRuleTest.java | 86 ++++++++++++++++++++++
 .../SingleMutableDataNodeRuleAttributeTest.java    |  3 +-
 .../manager/database/DatabaseMetaDataManager.java  |  3 +-
 .../database/DatabaseMetaDataManagerTest.java      | 10 +--
 .../CreateViewPushDownMetaDataRefresherTest.java   | 12 ++-
 12 files changed, 358 insertions(+), 16 deletions(-)

diff --git 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/metadata/database/ShardingSphereDatabase.java
 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/metadata/database/ShardingSphereDatabase.java
index 63bf79dfb00..f71a4894033 100644
--- 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/metadata/database/ShardingSphereDatabase.java
+++ 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/metadata/database/ShardingSphereDatabase.java
@@ -61,7 +61,7 @@ public final class ShardingSphereDatabase {
     
     private final ResourceMetaData resourceMetaData;
     
-    private final RuleMetaData ruleMetaData;
+    private volatile RuleMetaData ruleMetaData;
     
     private final DatabaseIdentifierContext identifierContext;
     
@@ -197,6 +197,39 @@ public final class ShardingSphereDatabase {
         return !resourceMetaData.getStorageUnits().isEmpty();
     }
     
+    /**
+     * Add data node and publish new rule meta data snapshot.
+     *
+     * @param dataSourceName data source name
+     * @param schemaName schema name
+     * @param tableName table name
+     * @return whether rule meta data changed
+     */
+    public synchronized boolean putDataNode(final String dataSourceName, final 
String schemaName, final String tableName) {
+        RuleMetaData newRuleMetaData = 
ruleMetaData.copyAndPutDataNode(dataSourceName, schemaName, tableName);
+        if (newRuleMetaData == ruleMetaData) {
+            return false;
+        }
+        ruleMetaData = newRuleMetaData;
+        return true;
+    }
+    
+    /**
+     * Remove data node and publish new rule meta data snapshot.
+     *
+     * @param schemaName schema name
+     * @param tableName table name
+     * @return whether rule meta data changed
+     */
+    public synchronized boolean removeDataNode(final String schemaName, final 
String tableName) {
+        RuleMetaData newRuleMetaData = 
ruleMetaData.copyAndRemoveDataNode(schemaName, tableName);
+        if (newRuleMetaData == ruleMetaData) {
+            return false;
+        }
+        ruleMetaData = newRuleMetaData;
+        return true;
+    }
+    
     /**
      * Reload rules.
      */
@@ -211,8 +244,7 @@ public final class ShardingSphereDatabase {
                     .collect(Collectors.toMap(Entry::getKey, entry -> 
entry.getValue().getDataSource(), (oldValue, currentValue) -> oldValue, 
LinkedHashMap::new));
             
rules.add(optional.getAttributes().getAttribute(MutableDataNodeRuleAttribute.class).reloadRule(ruleConfig,
 name, dataSources, rules));
         });
-        ruleMetaData.getRules().clear();
-        ruleMetaData.getRules().addAll(rules);
+        ruleMetaData = new RuleMetaData(rules);
     }
     
     /**
diff --git 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/metadata/database/rule/RuleMetaData.java
 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/metadata/database/rule/RuleMetaData.java
index 03607083bd0..5b1a70bd827 100644
--- 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/metadata/database/rule/RuleMetaData.java
+++ 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/metadata/database/rule/RuleMetaData.java
@@ -24,6 +24,7 @@ import 
org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
 import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
 import org.apache.shardingsphere.infra.rule.attribute.RuleAttribute;
 import 
org.apache.shardingsphere.infra.rule.attribute.datanode.DataNodeRuleAttribute;
+import 
org.apache.shardingsphere.infra.rule.attribute.datanode.MutableDataNodeRuleAttribute;
 import 
org.apache.shardingsphere.infra.rule.attribute.datasource.DataSourceMapperRuleAttribute;
 
 import java.util.Collection;
@@ -57,6 +58,51 @@ public final class RuleMetaData {
         this.rules = new CacheInvalidatingCopyOnWriteArrayList(rules);
     }
     
+    /**
+     * Copy rule meta data and add data node.
+     *
+     * @param dataSourceName data source name
+     * @param schemaName schema name
+     * @param tableName table name
+     * @return copied rule meta data, or current rule meta data if data node 
exists
+     */
+    public RuleMetaData copyAndPutDataNode(final String dataSourceName, final 
String schemaName, final String tableName) {
+        Collection<ShardingSphereRule> copiedRules = new LinkedList<>();
+        boolean changed = false;
+        for (ShardingSphereRule each : rules) {
+            Optional<MutableDataNodeRuleAttribute> attribute = 
each.getAttributes().findAttribute(MutableDataNodeRuleAttribute.class);
+            if (attribute.isPresent() && 
!attribute.get().findTableDataNode(schemaName, tableName).isPresent()) {
+                copiedRules.add(attribute.get().copyRuleAndPut(dataSourceName, 
schemaName, tableName));
+                changed = true;
+            } else {
+                copiedRules.add(each);
+            }
+        }
+        return changed ? new RuleMetaData(copiedRules) : this;
+    }
+    
+    /**
+     * Copy rule meta data and remove data node.
+     *
+     * @param schemaName schema name
+     * @param tableName table name
+     * @return copied rule meta data, or current rule meta data if data node 
does not exist
+     */
+    public RuleMetaData copyAndRemoveDataNode(final String schemaName, final 
String tableName) {
+        Collection<ShardingSphereRule> copiedRules = new LinkedList<>();
+        boolean changed = false;
+        for (ShardingSphereRule each : rules) {
+            Optional<MutableDataNodeRuleAttribute> attribute = 
each.getAttributes().findAttribute(MutableDataNodeRuleAttribute.class);
+            if (attribute.isPresent() && 
attribute.get().findTableDataNode(schemaName, tableName).isPresent()) {
+                copiedRules.add(attribute.get().copyRuleAndRemove(schemaName, 
tableName));
+                changed = true;
+            } else {
+                copiedRules.add(each);
+            }
+        }
+        return changed ? new RuleMetaData(copiedRules) : this;
+    }
+    
     /**
      * Get rule configurations.
      *
diff --git 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/attribute/datanode/MutableDataNodeRuleAttribute.java
 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/attribute/datanode/MutableDataNodeRuleAttribute.java
index d022f204d6f..7a6d9620118 100644
--- 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/attribute/datanode/MutableDataNodeRuleAttribute.java
+++ 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/attribute/datanode/MutableDataNodeRuleAttribute.java
@@ -32,6 +32,25 @@ import java.util.Optional;
  */
 public interface MutableDataNodeRuleAttribute extends RuleAttribute {
     
+    /**
+     * Copy rule and add data node.
+     *
+     * @param dataSourceName data source name
+     * @param schemaName schema name
+     * @param tableName table name
+     * @return copied rule with added data node
+     */
+    ShardingSphereRule copyRuleAndPut(String dataSourceName, String 
schemaName, String tableName);
+    
+    /**
+     * Copy rule and remove data node.
+     *
+     * @param schemaName schema name
+     * @param tableName table name
+     * @return copied rule with removed data node
+     */
+    ShardingSphereRule copyRuleAndRemove(String schemaName, String tableName);
+    
     /**
      * Add data node.
      *
diff --git 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/metadata/database/ShardingSphereDatabaseTest.java
 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/metadata/database/ShardingSphereDatabaseTest.java
index e8cd6597561..567837b4dc7 100644
--- 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/metadata/database/ShardingSphereDatabaseTest.java
+++ 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/metadata/database/ShardingSphereDatabaseTest.java
@@ -72,6 +72,8 @@ import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -178,6 +180,8 @@ class ShardingSphereDatabaseTest {
                 "foo_db", databaseType, new 
ResourceMetaData(Collections.singletonMap("ds", new MockedDataSource())), 
ruleMetaData, Collections.emptyList(),
                 new ConfigurationProperties(new Properties()));
         database.reloadRules();
+        assertThat(database.getRuleMetaData(), 
not(sameInstance(ruleMetaData)));
+        assertTrue(ruleMetaData.getRules().contains(mutableRule));
         Collection<ShardingSphereRule> actualRules = 
database.getRuleMetaData().getRules();
         assertThat(actualRules.size(), is(2));
         assertFalse(actualRules.contains(mutableRule));
@@ -185,6 +189,48 @@ class ShardingSphereDatabaseTest {
         assertTrue(actualRules.contains(reloadedRule));
     }
     
+    @Test
+    void assertPutDataNode() {
+        RuleMetaData original = mock(RuleMetaData.class);
+        RuleMetaData updated = mock(RuleMetaData.class);
+        when(original.copyAndPutDataNode("foo_ds", "foo_schema", 
"foo_tbl")).thenReturn(updated);
+        ShardingSphereDatabase database = 
createDatabaseWithRuleMetaData(original);
+        assertTrue(database.putDataNode("foo_ds", "foo_schema", "foo_tbl"));
+        assertThat(database.getRuleMetaData(), sameInstance(updated));
+    }
+    
+    @Test
+    void assertPutDataNodeWhenUnchanged() {
+        RuleMetaData original = mock(RuleMetaData.class);
+        when(original.copyAndPutDataNode("foo_ds", "foo_schema", 
"foo_tbl")).thenReturn(original);
+        ShardingSphereDatabase database = 
createDatabaseWithRuleMetaData(original);
+        assertFalse(database.putDataNode("foo_ds", "foo_schema", "foo_tbl"));
+        assertThat(database.getRuleMetaData(), sameInstance(original));
+    }
+    
+    @Test
+    void assertRemoveDataNode() {
+        RuleMetaData original = mock(RuleMetaData.class);
+        RuleMetaData updated = mock(RuleMetaData.class);
+        when(original.copyAndRemoveDataNode("foo_schema", 
"foo_tbl")).thenReturn(updated);
+        ShardingSphereDatabase database = 
createDatabaseWithRuleMetaData(original);
+        assertTrue(database.removeDataNode("foo_schema", "foo_tbl"));
+        assertThat(database.getRuleMetaData(), sameInstance(updated));
+    }
+    
+    @Test
+    void assertRemoveDataNodeWhenUnchanged() {
+        RuleMetaData original = mock(RuleMetaData.class);
+        when(original.copyAndRemoveDataNode("foo_schema", 
"foo_tbl")).thenReturn(original);
+        ShardingSphereDatabase database = 
createDatabaseWithRuleMetaData(original);
+        assertFalse(database.removeDataNode("foo_schema", "foo_tbl"));
+        assertThat(database.getRuleMetaData(), sameInstance(original));
+    }
+    
+    private ShardingSphereDatabase createDatabaseWithRuleMetaData(final 
RuleMetaData ruleMetaData) {
+        return new ShardingSphereDatabase("foo_db", databaseType, 
mock(ResourceMetaData.class), ruleMetaData, Collections.emptyList(), new 
ConfigurationProperties(new Properties()));
+    }
+    
     @Test
     void assertReloadRulesWithoutMutableDataNodeRuleAttribute() {
         ShardingSphereRule rule = createRule(mock(RuleConfiguration.class), 
new RuleAttributes());
diff --git 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/metadata/database/rule/RuleMetaDataTest.java
 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/metadata/database/rule/RuleMetaDataTest.java
index d422cd0756b..7b268a06917 100644
--- 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/metadata/database/rule/RuleMetaDataTest.java
+++ 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/metadata/database/rule/RuleMetaDataTest.java
@@ -22,6 +22,7 @@ import 
org.apache.shardingsphere.infra.rule.ShardingSphereRule;
 import org.apache.shardingsphere.infra.rule.attribute.RuleAttribute;
 import org.apache.shardingsphere.infra.rule.attribute.RuleAttributes;
 import 
org.apache.shardingsphere.infra.rule.attribute.datanode.DataNodeRuleAttribute;
+import 
org.apache.shardingsphere.infra.rule.attribute.datanode.MutableDataNodeRuleAttribute;
 import 
org.apache.shardingsphere.infra.rule.attribute.datasource.DataSourceMapperRuleAttribute;
 import org.apache.shardingsphere.infra.rule.scope.GlobalRule;
 import org.junit.jupiter.api.BeforeEach;
@@ -40,6 +41,8 @@ import java.util.stream.Stream;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.isA;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -163,6 +166,54 @@ class RuleMetaDataTest {
         assertFalse(new 
RuleMetaData(Collections.singleton(mock(ShardingSphereRule.class, 
RETURNS_DEEP_STUBS))).findAttribute(DataSourceMapperRuleAttribute.class).isPresent());
     }
     
+    @Test
+    void assertCopyAndPutDataNode() {
+        MutableDataNodeRuleAttribute attribute = 
mock(MutableDataNodeRuleAttribute.class);
+        ShardingSphereRule mutableRule = mock(ShardingSphereRule.class);
+        ShardingSphereRule copiedRule = mock(ShardingSphereRule.class);
+        when(mutableRule.getAttributes()).thenReturn(new 
RuleAttributes(attribute));
+        when(attribute.copyRuleAndPut("foo_ds", "foo_schema", 
"foo_tbl")).thenReturn(copiedRule);
+        RuleMetaData original = new RuleMetaData(Arrays.asList(fixtureRule, 
mutableRule, dataNodeRule));
+        RuleMetaData actual = original.copyAndPutDataNode("foo_ds", 
"foo_schema", "foo_tbl");
+        assertThat(actual, not(sameInstance(original)));
+        assertThat(actual.getRules(), is(Arrays.asList(fixtureRule, 
copiedRule, dataNodeRule)));
+        assertThat(original.getRules(), is(Arrays.asList(fixtureRule, 
mutableRule, dataNodeRule)));
+    }
+    
+    @Test
+    void assertCopyAndPutDataNodeWhenDataNodeExists() {
+        MutableDataNodeRuleAttribute attribute = 
mock(MutableDataNodeRuleAttribute.class);
+        ShardingSphereRule mutableRule = mock(ShardingSphereRule.class);
+        when(mutableRule.getAttributes()).thenReturn(new 
RuleAttributes(attribute));
+        when(attribute.findTableDataNode("foo_schema", 
"foo_tbl")).thenReturn(Optional.of(new DataNode("foo_ds.foo_schema.foo_tbl")));
+        RuleMetaData original = new 
RuleMetaData(Collections.singleton(mutableRule));
+        assertThat(original.copyAndPutDataNode("foo_ds", "foo_schema", 
"foo_tbl"), sameInstance(original));
+    }
+    
+    @Test
+    void assertCopyAndRemoveDataNode() {
+        MutableDataNodeRuleAttribute attribute = 
mock(MutableDataNodeRuleAttribute.class);
+        ShardingSphereRule mutableRule = mock(ShardingSphereRule.class);
+        ShardingSphereRule copiedRule = mock(ShardingSphereRule.class);
+        when(mutableRule.getAttributes()).thenReturn(new 
RuleAttributes(attribute));
+        when(attribute.findTableDataNode("foo_schema", 
"foo_tbl")).thenReturn(Optional.of(new DataNode("foo_ds.foo_schema.foo_tbl")));
+        when(attribute.copyRuleAndRemove("foo_schema", 
"foo_tbl")).thenReturn(copiedRule);
+        RuleMetaData original = new RuleMetaData(Arrays.asList(fixtureRule, 
mutableRule, dataNodeRule));
+        RuleMetaData actual = original.copyAndRemoveDataNode("foo_schema", 
"foo_tbl");
+        assertThat(actual, not(sameInstance(original)));
+        assertThat(actual.getRules(), is(Arrays.asList(fixtureRule, 
copiedRule, dataNodeRule)));
+        assertThat(original.getRules(), is(Arrays.asList(fixtureRule, 
mutableRule, dataNodeRule)));
+    }
+    
+    @Test
+    void assertCopyAndRemoveDataNodeWhenDataNodeDoesNotExist() {
+        MutableDataNodeRuleAttribute attribute = 
mock(MutableDataNodeRuleAttribute.class);
+        ShardingSphereRule mutableRule = mock(ShardingSphereRule.class);
+        when(mutableRule.getAttributes()).thenReturn(new 
RuleAttributes(attribute));
+        RuleMetaData original = new 
RuleMetaData(Collections.singleton(mutableRule));
+        assertThat(original.copyAndRemoveDataNode("foo_schema", "foo_tbl"), 
sameInstance(original));
+    }
+    
     @Test
     void assertInvalidateCacheWhenRuleAdded() {
         
ruleMetaData.findSingleRule(RuleMetaDataShardingSphereRuleFixture.class);
diff --git 
a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/SingleRule.java
 
b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/SingleRule.java
index 6731bbc2705..d969f516c9d 100644
--- 
a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/SingleRule.java
+++ 
b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/SingleRule.java
@@ -48,8 +48,11 @@ import 
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.Iden
 import javax.sql.DataSource;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.concurrent.ThreadLocalRandom;
 
@@ -86,12 +89,55 @@ public final class SingleRule implements DatabaseRule {
         this.protocolType = protocolType;
         singleTableDataNodes = SingleTableDataNodeLoader.load(databaseName, 
protocolType, aggregatedDataSources, builtRules, configuration.getTables());
         SingleTableMapperRuleAttribute tableMapperRuleAttribute = new 
SingleTableMapperRuleAttribute(singleTableDataNodes.values());
-        mutableDataNodeRuleAttribute = new 
SingleMutableDataNodeRuleAttribute(configuration, dataSourceNames, 
singleTableDataNodes, protocolType, tableMapperRuleAttribute);
+        mutableDataNodeRuleAttribute = new 
SingleMutableDataNodeRuleAttribute(this, configuration, dataSourceNames, 
singleTableDataNodes, protocolType, tableMapperRuleAttribute);
         attributes = new RuleAttributes(new 
SingleDataNodeRuleAttribute(singleTableDataNodes), tableMapperRuleAttribute,
                 new SingleExportableRuleAttribute(tableMapperRuleAttribute), 
mutableDataNodeRuleAttribute, new 
AggregatedDataSourceRuleAttribute(aggregatedDataSources),
                 new SingleUnregisterStorageUnitRuleAttribute());
     }
     
+    private SingleRule(final SingleRule original) {
+        configuration = new SingleRuleConfiguration(new 
LinkedList<>(original.configuration.getTables()), original.defaultDataSource);
+        defaultDataSource = original.defaultDataSource;
+        dataSourceNames = new CaseInsensitiveSet<>(original.dataSourceNames);
+        singleTableDataNodes = new 
LinkedHashMap<>(original.singleTableDataNodes.size(), 1F);
+        for (Entry<String, Collection<DataNode>> entry : 
original.singleTableDataNodes.entrySet()) {
+            singleTableDataNodes.put(entry.getKey(), new 
LinkedHashSet<>(entry.getValue()));
+        }
+        protocolType = original.protocolType;
+        SingleTableMapperRuleAttribute tableMapperRuleAttribute = new 
SingleTableMapperRuleAttribute(singleTableDataNodes.values());
+        mutableDataNodeRuleAttribute = new 
SingleMutableDataNodeRuleAttribute(this, configuration, dataSourceNames, 
singleTableDataNodes, protocolType, tableMapperRuleAttribute);
+        attributes = new RuleAttributes(new 
SingleDataNodeRuleAttribute(singleTableDataNodes), tableMapperRuleAttribute,
+                new SingleExportableRuleAttribute(tableMapperRuleAttribute), 
mutableDataNodeRuleAttribute,
+                
original.attributes.getAttribute(AggregatedDataSourceRuleAttribute.class), new 
SingleUnregisterStorageUnitRuleAttribute());
+    }
+    
+    /**
+     * Copy rule and add data node.
+     *
+     * @param dataSourceName data source name
+     * @param schemaName schema name
+     * @param tableName table name
+     * @return copied rule with added data node
+     */
+    public SingleRule copyAndPut(final String dataSourceName, final String 
schemaName, final String tableName) {
+        SingleRule result = new SingleRule(this);
+        result.mutableDataNodeRuleAttribute.put(dataSourceName, schemaName, 
tableName);
+        return result;
+    }
+    
+    /**
+     * Copy rule and remove data node.
+     *
+     * @param schemaName schema name
+     * @param tableName table name
+     * @return copied rule with removed data node
+     */
+    public SingleRule copyAndRemove(final String schemaName, final String 
tableName) {
+        SingleRule result = new SingleRule(this);
+        result.mutableDataNodeRuleAttribute.remove(schemaName, tableName);
+        return result;
+    }
+    
     /**
      * Assign new data source name.
      *
diff --git 
a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleMutableDataNodeRuleAttribute.java
 
b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleMutableDataNodeRuleAttribute.java
index 2eedd5f9dc1..191283b35d5 100644
--- 
a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleMutableDataNodeRuleAttribute.java
+++ 
b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleMutableDataNodeRuleAttribute.java
@@ -43,6 +43,8 @@ import java.util.Optional;
 @RequiredArgsConstructor
 public final class SingleMutableDataNodeRuleAttribute implements 
MutableDataNodeRuleAttribute {
     
+    private final SingleRule singleRule;
+    
     private final SingleRuleConfiguration configuration;
     
     private final Collection<String> dataSourceNames;
@@ -53,6 +55,16 @@ public final class SingleMutableDataNodeRuleAttribute 
implements MutableDataNode
     
     private final SingleTableMapperRuleAttribute tableMapperRuleAttribute;
     
+    @Override
+    public ShardingSphereRule copyRuleAndPut(final String dataSourceName, 
final String schemaName, final String tableName) {
+        return singleRule.copyAndPut(dataSourceName, schemaName, tableName);
+    }
+    
+    @Override
+    public ShardingSphereRule copyRuleAndRemove(final String schemaName, final 
String tableName) {
+        return singleRule.copyAndRemove(schemaName, tableName);
+    }
+    
     @SuppressWarnings("CollectionWithoutInitialCapacity")
     @Override
     public void put(final String dataSourceName, final String schemaName, 
final String tableName) {
diff --git 
a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/SingleRuleTest.java
 
b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/SingleRuleTest.java
index 73a5ecc1213..c3a9eb8c226 100644
--- 
a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/SingleRuleTest.java
+++ 
b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/SingleRuleTest.java
@@ -22,8 +22,11 @@ import 
org.apache.shardingsphere.database.connector.core.metadata.identifier.Ide
 import 
org.apache.shardingsphere.database.connector.core.metadata.identifier.LookupMode;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import 
org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
+import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
 import org.apache.shardingsphere.infra.datanode.DataNode;
 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.metadata.database.schema.QualifiedTable;
 import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
 import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
@@ -63,11 +66,18 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Locale;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -241,6 +251,82 @@ class SingleRuleTest {
         assertTrue(actualLogicTableNames.contains("t_order_1"));
     }
     
+    @Test
+    void assertCopyAndPut() {
+        SingleRule original = new SingleRule(ruleConfig, "foo_db", 
databaseType, dataSourceMap, 
Collections.singleton(mock(ShardingSphereRule.class, RETURNS_DEEP_STUBS)));
+        SingleRule copied = original.copyAndPut("foo_ds", "foo_db", "teacher");
+        assertThat(copied, not(sameInstance(original)));
+        assertFalse(original.getSingleTableDataNodes().containsKey("teacher"));
+        
assertFalse(original.getAttributes().getAttribute(TableMapperRuleAttribute.class).getLogicTableNames().contains("teacher"));
+        
assertFalse(original.getConfiguration().getLogicTableNames().contains("teacher"));
+        assertTrue(copied.getSingleTableDataNodes().containsKey("teacher"));
+        
assertTrue(copied.getAttributes().getAttribute(TableMapperRuleAttribute.class).getLogicTableNames().contains("teacher"));
+        
assertTrue(copied.getConfiguration().getLogicTableNames().contains("teacher"));
+    }
+    
+    @Test
+    void assertCopyAndPutWithWildcardConfiguration() {
+        SingleRuleConfiguration wildcardConfig = new 
SingleRuleConfiguration(Collections.singleton("*.*"), null);
+        SingleRule original = new SingleRule(wildcardConfig, "foo_db", 
databaseType, dataSourceMap, 
Collections.singleton(mock(ShardingSphereRule.class, RETURNS_DEEP_STUBS)));
+        SingleRule copied = original.copyAndPut("foo_ds", "foo_db", "teacher");
+        assertThat(copied.getConfiguration().getTables().size(), is(1));
+        assertTrue(copied.getConfiguration().getTables().contains("*.*"));
+        assertThat(original.getConfiguration().getTables().size(), is(1));
+        assertTrue(original.getConfiguration().getTables().contains("*.*"));
+    }
+    
+    @Test
+    void assertCopyAndRemove() {
+        SingleRule original = new SingleRule(ruleConfig, "foo_db", 
databaseType, dataSourceMap, 
Collections.singleton(mock(ShardingSphereRule.class, RETURNS_DEEP_STUBS)));
+        SingleRule copied = original.copyAndRemove("foo_db", "employee");
+        assertThat(copied, not(sameInstance(original)));
+        assertTrue(original.getSingleTableDataNodes().containsKey("employee"));
+        
assertTrue(original.getAttributes().getAttribute(TableMapperRuleAttribute.class).getLogicTableNames().contains("employee"));
+        
assertTrue(original.getConfiguration().getLogicTableNames().contains("employee"));
+        assertFalse(copied.getSingleTableDataNodes().containsKey("employee"));
+        
assertFalse(copied.getAttributes().getAttribute(TableMapperRuleAttribute.class).getLogicTableNames().contains("employee"));
+        
assertFalse(copied.getConfiguration().getLogicTableNames().contains("employee"));
+    }
+    
+    @Test
+    void assertConcurrentRuleMetaDataPublication() throws Exception {
+        SingleRule original = new SingleRule(ruleConfig, "foo_db", 
databaseType, dataSourceMap, 
Collections.singleton(mock(ShardingSphereRule.class, RETURNS_DEEP_STUBS)));
+        ShardingSphereDatabase database = new ShardingSphereDatabase("foo_db", 
databaseType, mock(ResourceMetaData.class),
+                new RuleMetaData(Collections.singleton(original)), 
Collections.emptyList(), new ConfigurationProperties(new Properties()));
+        CountDownLatch writing = new CountDownLatch(1);
+        ExecutorService executorService = Executors.newFixedThreadPool(2);
+        try {
+            Future<?> writer = executorService.submit(() -> {
+                try {
+                    for (int i = 0; i < 1000; i++) {
+                        database.putDataNode("foo_ds", "foo_db", "teacher");
+                        database.removeDataNode("foo_db", "teacher");
+                    }
+                } finally {
+                    writing.countDown();
+                }
+            });
+            Future<?> reader = executorService.submit(() -> {
+                do {
+                    assertSingleRuleSnapshot(database.getRuleMetaData(), 
"teacher");
+                } while (0L < writing.getCount());
+            });
+            writer.get();
+            reader.get();
+        } finally {
+            executorService.shutdownNow();
+        }
+        assertFalse(original.getSingleTableDataNodes().containsKey("teacher"));
+    }
+    
+    private void assertSingleRuleSnapshot(final RuleMetaData ruleMetaData, 
final String tableName) {
+        assertFalse(ruleMetaData.getRules().isEmpty());
+        SingleRule rule = ruleMetaData.getSingleRule(SingleRule.class);
+        boolean dataNodePresent = 
rule.getSingleTableDataNodes().containsKey(tableName);
+        
assertThat(rule.getAttributes().getAttribute(TableMapperRuleAttribute.class).getLogicTableNames().contains(tableName),
 is(dataNodePresent));
+        
assertThat(rule.getConfiguration().getLogicTableNames().contains(tableName), 
is(dataNodePresent));
+    }
+    
     @Test
     void assertGetAllDataNodes() {
         ShardingSphereRule builtRule = mock(ShardingSphereRule.class, 
RETURNS_DEEP_STUBS);
diff --git 
a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/attribute/SingleMutableDataNodeRuleAttributeTest.java
 
b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/attribute/SingleMutableDataNodeRuleAttributeTest.java
index cd208782b42..f1a05e91523 100644
--- 
a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/attribute/SingleMutableDataNodeRuleAttributeTest.java
+++ 
b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/attribute/SingleMutableDataNodeRuleAttributeTest.java
@@ -44,6 +44,7 @@ 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.assertTrue;
+import static org.mockito.Mockito.mock;
 
 class SingleMutableDataNodeRuleAttributeTest {
     
@@ -223,7 +224,7 @@ class SingleMutableDataNodeRuleAttributeTest {
     private static SingleMutableDataNodeRuleAttribute 
createRuleAttribute(final SingleRuleConfiguration configuration, final 
Collection<String> dataSourceNames,
                                                                           
final Map<String, Collection<DataNode>> singleTableDataNodes,
                                                                           
final SingleTableMapperRuleAttribute tableMapperRuleAttribute) {
-        return new SingleMutableDataNodeRuleAttribute(configuration, new 
LinkedHashSet<>(dataSourceNames), singleTableDataNodes, DATABASE_TYPE, 
tableMapperRuleAttribute);
+        return new SingleMutableDataNodeRuleAttribute(mock(SingleRule.class), 
configuration, new LinkedHashSet<>(dataSourceNames), singleTableDataNodes, 
DATABASE_TYPE, tableMapperRuleAttribute);
     }
     
     private static Collection<String> createDataNodeStrings(final 
Collection<DataNode> dataNodes) {
diff --git 
a/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/database/DatabaseMetaDataManager.java
 
b/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/database/DatabaseMetaDataManager.java
index 551cf2c06b1..96ce015953b 100644
--- 
a/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/database/DatabaseMetaDataManager.java
+++ 
b/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/database/DatabaseMetaDataManager.java
@@ -25,7 +25,6 @@ import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
 import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
 import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereView;
-import 
org.apache.shardingsphere.infra.rule.attribute.datanode.MutableDataNodeRuleAttribute;
 import org.apache.shardingsphere.infra.rule.scope.GlobalRule;
 import 
org.apache.shardingsphere.infra.rule.scope.GlobalRule.GlobalRuleChangedType;
 import org.apache.shardingsphere.mode.metadata.MetaDataContexts;
@@ -193,7 +192,7 @@ public final class DatabaseMetaDataManager {
         } else {
             
database.getSchema(schemaName).removeView(toBeDroppedTableOrViewName);
         }
-        
database.getRuleMetaData().getAttributes(MutableDataNodeRuleAttribute.class).forEach(each
 -> each.remove(schemaName, toBeDroppedTableOrViewName));
+        database.removeDataNode(schemaName, toBeDroppedTableOrViewName);
         metaDataContexts.getMetaData().getGlobalRuleMetaData().getRules()
                 .forEach(each -> ((GlobalRule) 
each).refresh(metaDataContexts.getMetaData().getAllDatabases(), 
GlobalRuleChangedType.SCHEMA_CHANGED));
     }
diff --git 
a/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/database/DatabaseMetaDataManagerTest.java
 
b/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/database/DatabaseMetaDataManagerTest.java
index b58c30b1ee4..534f1cb5f08 100644
--- 
a/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/database/DatabaseMetaDataManagerTest.java
+++ 
b/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/database/DatabaseMetaDataManagerTest.java
@@ -26,7 +26,6 @@ import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSp
 import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
 import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereView;
 import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
-import 
org.apache.shardingsphere.infra.rule.attribute.datanode.MutableDataNodeRuleAttribute;
 import org.apache.shardingsphere.infra.rule.scope.GlobalRule;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.apache.shardingsphere.mode.metadata.MetaDataContexts;
@@ -230,17 +229,17 @@ class DatabaseMetaDataManagerTest {
     @Test
     void assertDropTable() {
         
when(metaDataContexts.getMetaData().getDatabase("foo_db").getAllSchemas()).thenReturn(Collections.singleton(createToBeAlteredSchema()));
-        mockMutableDataNodeRuleAttribute();
         databaseMetaDataManager.dropTable("foo_db", "foo_schema", "foo_tbl");
         
assertFalse(metaDataContexts.getMetaData().getDatabase("foo_db").getSchema("foo_schema").containsTable("foo_tbl"));
+        
verify(metaDataContexts.getMetaData().getDatabase("foo_db")).removeDataNode("foo_schema",
 "foo_tbl");
     }
     
     @Test
     void assertDropView() {
         
when(metaDataContexts.getMetaData().getDatabase("foo_db").getAllSchemas()).thenReturn(Collections.singleton(createToBeAlteredSchema()));
-        mockMutableDataNodeRuleAttribute();
         databaseMetaDataManager.dropView("foo_db", "foo_schema", "foo_view");
         
assertFalse(metaDataContexts.getMetaData().getDatabase("foo_db").getSchema("foo_schema").containsView("foo_view"));
+        
verify(metaDataContexts.getMetaData().getDatabase("foo_db")).removeDataNode("foo_schema",
 "foo_view");
     }
     
     @Test
@@ -257,11 +256,6 @@ class DatabaseMetaDataManagerTest {
         verify(metaDataContexts.getMetaData().getDatabase("foo_db"), 
never()).getSchema(anyString());
     }
     
-    private void mockMutableDataNodeRuleAttribute() {
-        MutableDataNodeRuleAttribute attribute = 
mock(MutableDataNodeRuleAttribute.class);
-        
when(metaDataContexts.getMetaData().getDatabase("foo_db").getRuleMetaData().getAttributes(MutableDataNodeRuleAttribute.class)).thenReturn(Collections.singleton(attribute));
-    }
-    
     private ShardingSphereSchema createToBeAlteredSchema() {
         ShardingSphereTable beforeChangedTable = new 
ShardingSphereTable("foo_tbl", Collections.emptyList(), 
Collections.emptyList(), Collections.emptyList());
         ShardingSphereView beforeChangedView = new 
ShardingSphereView("foo_view", "");
diff --git 
a/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/refresher/pushdown/type/view/CreateViewPushDownMetaDataRefresherTest.java
 
b/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/refresher/pushdown/type/view/CreateViewPushDownMetaDataRefresherTest.java
index 1e4f99adc09..3084f9e8015 100644
--- 
a/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/refresher/pushdown/type/view/CreateViewPushDownMetaDataRefresherTest.java
+++ 
b/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/refresher/pushdown/type/view/CreateViewPushDownMetaDataRefresherTest.java
@@ -168,6 +168,16 @@ class CreateViewPushDownMetaDataRefresherTest {
         
         private final DataNode dataNode;
         
+        @Override
+        public ShardingSphereRule copyRuleAndPut(final String dataSourceName, 
final String schemaName, final String tableName) {
+            return new SingleTableRule(new DataNode(dataSourceName, 
schemaName, tableName));
+        }
+        
+        @Override
+        public ShardingSphereRule copyRuleAndRemove(final String schemaName, 
final String tableName) {
+            return new SingleTableRule(dataNode);
+        }
+        
         @Override
         public void put(final String dataSourceName, final String schemaName, 
final String tableName) {
         }
@@ -182,7 +192,7 @@ class CreateViewPushDownMetaDataRefresherTest {
         
         @Override
         public Optional<DataNode> findTableDataNode(final String schemaName, 
final String tableName) {
-            return Optional.of(dataNode);
+            return dataNode.getSchemaName().equals(schemaName) && 
dataNode.getTableName().equals(tableName) ? Optional.of(dataNode) : 
Optional.empty();
         }
         
         @Override

Reply via email to