Repository: apache/shardingsphere (Java)

Issue:
Create sharding table rules with same data nodes will cause exception
## Bug Report
### Which version of ShardingSphere did you use?
5.5.1-SNAPSHOT master ec7ce3b9574626ab4a1b716b09aba0a3eaa74207

### Which project did you use? ShardingSphere-JDBC or ShardingSphere-Proxy?
ShardingSphere-Proxy

### Expected behavior
Feedback exception when executing DistSQL instead of success

### Actual behavior
DistSQL was executed successfully, but an exception occurred when refreshing metadata

### Reason analyze (If you can)
#29724 added validation in rule, but DistSQL has no corresponding logic

### Steps to reproduce the behavior, such as: SQL to execute, sharding rule configuration, when exception occur etc.
1. create sharding table rule
```sql
CREATE SHARDING TABLE RULE tc (
DATANODES("ds_${0..1}.tc"),
DATABASE_STRATEGY(TYPE='standard',SHARDING_COLUMN=id,SHARDING_ALGORITHM(TYPE(NAME='inline',PROPERTIES('algorithm-expression'='ds_${id % 2}'))))
);

CREATE SHARDING TABLE RULE td (
DATANODES("ds_${0..1}.tc"),
DATABASE_STRATEGY(TYPE='standard',SHARDING_COLUMN=id,SHARDING_ALGORITHM(TYPE(NAME='inline',PROPERTIES('algorithm-expression'='ds_${id % 2}'))))
);
```
2. log of proxy
```plain
Exception in thread "main" org.apache.shardingsphere.sharding.exception.metadata.DuplicateSharingActualDataNodeException: Same actual data node cannot be configured in multiple logic tables in same database, logical table 'tc', actual data node 'ds_0.tc'.
	at org.apache.shardingsphere.sharding.rule.checker.ShardingRuleChecker.lambda$checkUniqueActualDataNodesInTableRules$0(ShardingRuleChecker.java:71)
	at org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions.checkNotContains(ShardingSpherePreconditions.java:158)
	at org.apache.shardingsphere.sharding.rule.checker.ShardingRuleChecker.lambda$checkUniqueActualDataNodesInTableRules$1(ShardingRuleChecker.java:70)
	at java.base/java.util.Map.forEach(Map.java:713)
	at org.apache.shardingsphere.sharding.rule.checker.ShardingRuleChecker.checkUniqueActualDataNodesInTableRules(ShardingRuleChecker.java:68)
	at org.apache.shardingsphere.sharding.rule.checker.ShardingRuleChecker.check(ShardingRuleChecker.java:61)
	at org.apache.shardingsphere.sharding.rule.ShardingRule.<init>(ShardingRule.java:142)
	at org.apache.shardingsphere.sharding.rule.builder.ShardingRuleBuilder.build(ShardingRuleBuilder.java:42)
	at org.apache.shardingsphere.sharding.rule.builder.ShardingRuleBuilder.build(ShardingRuleBuilder.java:36)
	at org.apache.shardingsphere.infra.rule.builder.database.DatabaseRulesBuilder.build(DatabaseRulesBuilder.java:69)
	at org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase.create(ShardingSphereDatabase.java:127)
```





Fix (patch):
diff --git a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/rule/ShardingRule.java b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/rule/ShardingRule.java
index 393caab49642a..d6c0938ef5a5d 100644
--- a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/rule/ShardingRule.java
+++ b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/rule/ShardingRule.java
@@ -84,8 +84,6 @@
 @Getter
 public final class ShardingRule implements DatabaseRule {
     
-    private static final String ALGORITHM_EXPRESSION_KEY = "algorithm-expression";
-    
     private final ShardingRuleConfiguration configuration;
     
     private final Collection<String> dataSourceNames;
diff --git a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/rule/checker/ShardingRuleChecker.java b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/rule/checker/ShardingRuleChecker.java
index 41e55f1613a7c..4713cd900a6c0 100644
--- a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/rule/checker/ShardingRuleChecker.java
+++ b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/rule/checker/ShardingRuleChecker.java
@@ -44,7 +44,6 @@
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 @RequiredArgsConstructor
@@ -64,13 +63,14 @@ public void check(final ShardingRuleConfiguration ruleConfig) {
     }
     
     private void checkUniqueActualDataNodesInTableRules() {
-        Set<DataNode> uniqueActualDataNodes = new HashSet<>(shardingRule.getShardingTables().size(), 1F);
-        shardingRule.getShardingTables().forEach((key, value) -> {
-            DataNode sampleActualDataNode = value.getActualDataNodes().iterator().next();
-            ShardingSpherePreconditions.checkNotContains(uniqueActualDataNodes, sampleActualDataNode,
-                    () -> new DuplicateShardingActualDataNodeException(key, sampleActualDataNode.getDataSourceName(), sampleActualDataNode.getTableName()));
-            uniqueActualDataNodes.add(sampleActualDataNode);
-        });
+        Collection<DataNode> uniqueActualDataNodes = new HashSet<>(shardingRule.getShardingTables().size(), 1F);
+        shardingRule.getShardingTables().forEach((key, value) -> checkUniqueActualDataNodes(uniqueActualDataNodes, key, value.getActualDataNodes().iterator().next()));
+    }
+    
+    private void checkUniqueActualDataNodes(final Collection<DataNode> uniqueActualDataNodes, final String logicTable, final DataNode sampleActualDataNode) {
+        ShardingSpherePreconditions.checkNotContains(uniqueActualDataNodes, sampleActualDataNode,
+                () -> new DuplicateShardingActualDataNodeException(logicTable, sampleActualDataNode.getDataSourceName(), sampleActualDataNode.getTableName()));
+        uniqueActualDataNodes.add(sampleActualDataNode);
     }
     
     private void checkBindingTableConfiguration(final ShardingRuleConfiguration ruleConfig) {
@@ -178,4 +178,21 @@ private void validateInlineShardingAlgorithm(final ShardingTable shardingTable,
                             shardingStrategy.getShardingAlgorithmName(), shardingTable.getLogicTable()));
         }
     }
+    
+    /**
+     * Check to be added data nodes.
+     *
+     * @param toBeAddedDataNodes to be added data nodes
+     * @param isAlteration is alteration
+     */
+    public void checkToBeAddedDataNodes(final Map<String, Collection<DataNode>> toBeAddedDataNodes, final boolean isAlteration) {
+        Collection<DataNode> uniqueActualDataNodes = new HashSet<>(shardingRule.getShardingTables().size() + toBeAddedDataNodes.size(), 1F);
+        shardingRule.getShardingTables().forEach((key, value) -> {
+            if (isAlteration && toBeAddedDataNodes.containsKey(key)) {
+                return;
+            }
+            checkUniqueActualDataNodes(uniqueActualDataNodes, key, value.getActualDataNodes().iterator().next());
+        });
+        toBeAddedDataNodes.forEach((key, value) -> checkUniqueActualDataNodes(uniqueActualDataNodes, key, value.iterator().next()));
+    }
 }
diff --git a/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/checker/ShardingTableRuleStatementChecker.java b/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/checker/ShardingTableRuleStatementChecker.java
index bf51ee86f331b..eb5c7407e4e34 100644
--- a/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/checker/ShardingTableRuleStatementChecker.java
+++ b/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/checker/ShardingTableRuleStatementChecker.java
@@ -55,6 +55,7 @@
 import org.apache.shardingsphere.sharding.distsql.segment.table.AutoTableRuleSegment;
 import org.apache.shardingsphere.sharding.distsql.segment.table.TableRuleSegment;
 import org.apache.shardingsphere.sharding.exception.algorithm.ShardingAlgorithmClassImplementationException;
+import org.apache.shardingsphere.sharding.exception.metadata.DuplicateShardingActualDataNodeException;
 import org.apache.shardingsphere.sharding.exception.metadata.ShardingTableRuleNotFoundException;
 import org.apache.shardingsphere.sharding.exception.strategy.InvalidShardingStrategyConfigurationException;
 import org.apache.shardingsphere.sharding.rule.BindingTableCheckedConfiguration;
@@ -525,4 +526,19 @@ private static Collection<String> getAlteredLogicalTableNames(final ShardingRule
         result.addAll(toBeAlteredRuleConfig.getAutoTables().stream().map(ShardingAutoTableRuleConfiguration::getLogicTable).collect(Collectors.toList()));
         return result;
     }
+    
+    /**
+     * Check to be added data nodes.
+     *
+     * @param toBeAddedDataNodes to be added data nodes
+     */
+    public static void checkToBeAddedDataNodes(final Map<String, Collection<DataNode>> toBeAddedDataNodes) {
+        Collection<DataNode> uniqueActualDataNodes = new HashSet<>(toBeAddedDataNodes.size(), 1F);
+        toBeAddedDataNodes.forEach((key, value) -> {
+            DataNode sampleActualDataNode = value.iterator().next();
+            ShardingSpherePreconditions.checkNotContains(uniqueActualDataNodes, sampleActualDataNode,
+                    () -> new DuplicateShardingActualDataNodeException(key, sampleActualDataNode.getDataSourceName(), sampleActualDataNode.getTableName()));
+            uniqueActualDataNodes.add(sampleActualDataNode);
+        });
+    }
 }
diff --git a/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/converter/ShardingTableRuleStatementConverter.java b/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/converter/ShardingTableRuleStatementConverter.java
index 89822c253a6fc..3a5502076a356 100644
--- a/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/converter/ShardingTableRuleStatementConverter.java
+++ b/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/converter/ShardingTableRuleStatementConverter.java
@@ -21,6 +21,12 @@
 import lombok.NoArgsConstructor;
 import org.apache.shardingsphere.distsql.segment.AlgorithmSegment;
 import org.apache.shardingsphere.infra.algorithm.core.config.AlgorithmConfiguration;
+import org.apache.shardingsphere.infra.algorithm.core.exception.AlgorithmInitializationException;
+import org.apache.shardingsphere.infra.datanode.DataNode;
+import org.apache.shardingsphere.infra.datanode.DataNodeUtils;
+import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions;
+import org.apache.shardingsphere.infra.expr.core.InlineExpressionParserFactory;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
 import org.apache.shardingsphere.sharding.api.config.rule.ShardingAutoTableRuleConfiguration;
 import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
@@ -28,6 +34,7 @@
 import org.apache.shardingsphere.sharding.api.config.strategy.keygen.KeyGenerateStrategyConfiguration;
 import org.apache.shardingsphere.sharding.api.config.strategy.sharding.NoneShardingStrategyConfiguration;
 import org.apache.shardingsphere.sharding.api.config.strategy.sharding.ShardingStrategyConfiguration;
+import org.apache.shardingsphere.sharding.api.sharding.ShardingAutoTableAlgorithm;
 import org.apache.shardingsphere.sharding.distsql.handler.enums.ShardingStrategyLevelType;
 import org.apache.shardingsphere.sharding.distsql.handler.enums.ShardingStrategyType;
 import org.apache.shardingsphere.sharding.distsql.segment.strategy.AuditStrategySegment;
@@ -37,9 +44,12 @@
 import org.apache.shardingsphere.sharding.distsql.segment.table.AbstractTableRuleSegment;
 import org.apache.shardingsphere.sharding.distsql.segment.table.AutoTableRuleSegment;
 import org.apache.shardingsphere.sharding.distsql.segment.table.TableRuleSegment;
+import org.apache.shardingsphere.sharding.spi.ShardingAlgorithm;
 
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -195,4 +205,41 @@ private static String getTableShardingAlgorithmName(final String tableName, fina
     private static String getKeyGeneratorName(final String tableName, final String algorithmType) {
         return String.format("%s_%s", tableName, algorithmType).toLowerCase();
     }
+    
+    /**
+     * Convert rule segments to data nodes.
+     *
+     * @param segments sharding table rule segments
+     * @return data nodes map
+     */
+    public static Map<String, Collection<DataNode>> convertDataNodes(final Collection<AbstractTableRuleSegment> segments) {
+        Map<String, Collection<DataNode>> result = new HashMap<>(segments.size(), 1F);
+        for (AbstractTableRuleSegment each : segments) {
+            if (each instanceof TableRuleSegment) {
+                result.put(each.getLogicTable(), getActualDataNodes((TableRuleSegment) each));
+                continue;
+            }
+            result.put(each.getLogicTable(), getActualDataNodes((AutoTableRuleSegment) each));
+        }
+        return result;
+    }
+    
+    private static Collection<DataNode> getActualDataNodes(final TableRuleSegment ruleSegment) {
+        Collection<DataNode> result = new LinkedList<>();
+        for (String each : ruleSegment.getDataSourceNodes()) {
+            List<String> dataNodes = InlineExpressionParserFactory.newInstance(each).splitAndEvaluate();
+            result.addAll(dataNodes.stream().map(DataNode::new).collect(Collectors.toList()));
+        }
+        return result;
+    }
+    
+    private static Collection<DataNode> getActualDataNodes(final AutoTableRuleSegment ruleSegment) {
+        ShardingAlgorithm shardingAlgorithm =
+                TypedSPILoader.getService(ShardingAlgorithm.class, ruleSegment.getShardingAlgorithmSegment().getName(), ruleSegment.getShardingAlgorithmSegment().getProps());
+        ShardingSpherePreconditions.checkState(shardingAlgorithm instanceof ShardingAutoTableAlgorithm,
+                () -> new AlgorithmInitializationException(shardingAlgorithm, "Auto sharding algorithm is required for table '%s'", ruleSegment.getLogicTable()));
+        List<String> dataNodes = DataNodeUtils.getFormatDataNodes(((ShardingAutoTableAlgorithm) shardingAlgorithm).getAutoTablesAmount(),
+                ruleSegment.getLogicTable(), ruleSegment.getDataSourceNodes());
+        return dataNodes.stream().map(DataNode::new).collect(Collectors.toList());
+    }
 }
diff --git a/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/update/AlterShardingTableRuleExecutor.java b/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/update/AlterShardingTableRuleExecutor.java
index 3263560a1a813..49080b33a000b 100644
--- a/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/update/AlterShardingTableRuleExecutor.java
+++ b/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/update/AlterShardingTableRuleExecutor.java
@@ -18,8 +18,8 @@
 package org.apache.shardingsphere.sharding.distsql.handler.update;
 
 import lombok.Setter;
-import org.apache.shardingsphere.distsql.handler.required.DistSQLExecutorCurrentRuleRequired;
 import org.apache.shardingsphere.distsql.handler.engine.update.rdl.rule.spi.database.DatabaseRuleAlterExecutor;
+import org.apache.shardingsphere.distsql.handler.required.DistSQLExecutorCurrentRuleRequired;
 import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
 import org.apache.shardingsphere.sharding.api.config.rule.ShardingAutoTableRuleConfiguration;
@@ -46,6 +46,11 @@ public final class AlterShardingTableRuleExecutor implements DatabaseRuleAlterEx
     @Override
     public void checkBeforeUpdate(final AlterShardingTableRuleStatement sqlStatement) {
         ShardingTableRuleStatementChecker.checkAlteration(database, sqlStatement.getRules(), rule.getConfiguration());
+        checkUniqueActualDataNodes(sqlStatement);
+    }
+    
+    private void checkUniqueActualDataNodes(final AlterShardingTableRuleStatement sqlStatement) {
+        rule.getShardingRuleChecker().checkToBeAddedDataNodes(ShardingTableRuleStatementConverter.convertDataNodes(sqlStatement.getRules()), true);
     }
     
     @Override
diff --git a/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/update/CreateShardingTableRuleExecutor.java b/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/update/CreateShardingTableRuleExecutor.java
index 7dbcdf6ceb887..d842b3bd78b06 100644
--- a/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/update/CreateShardingTableRuleExecutor.java
+++ b/features/sharding/distsql/handler/src/main/java/org/apache/shardingsphere/sharding/distsql/handler/update/CreateShardingTableRuleExecutor.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.sharding.distsql.handler.update;
 
+import com.cedarsoftware.util.CaseInsensitiveSet;
 import lombok.Setter;
 import org.apache.shardingsphere.distsql.handler.engine.update.rdl.rule.spi.database.DatabaseRuleCreateExecutor;
 import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
@@ -31,7 +32,6 @@
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedList;
 import java.util.stream.Collectors;
 
 /**
@@ -47,16 +47,24 @@ public final class CreateShardingTableRuleExecutor implements DatabaseRuleCreate
     @Override
     public void checkBeforeUpdate(final CreateShardingTableRuleStatement sqlStatement) {
         ShardingTableRuleStatementChecker.checkCreation(database, sqlStatement.getRules(), sqlStatement.isIfNotExists(), null == rule ? null : rule.getConfiguration());
+        checkUniqueActualDataNodes(sqlStatement);
     }
     
-    @Override
-    public ShardingRuleConfiguration buildToBeCreatedRuleConfiguration(final CreateShardingTableRuleStatement sqlStatement) {
-        Collection<AbstractTableRuleSegment> segments = sqlStatement.getRules();
+    private void checkUniqueActualDataNodes(final CreateShardingTableRuleStatement sqlStatement) {
+        if (null == rule) {
+            ShardingTableRuleStatementChecker.checkToBeAddedDataNodes(ShardingTableRuleStatementConverter.convertDataNodes(sqlStatement.getRules()));
+            return;
+        }
         if (sqlStatement.isIfNotExists()) {
             Collection<String> duplicatedRuleNames = getDuplicatedRuleNames(sqlStatement);
-            segments.removeIf(each -> duplicatedRuleNames.contains(each.getLogicTable()));
+            sqlStatement.getRules().removeIf(each -> duplicatedRuleNames.contains(each.getLogicTable()));
         }
-        return ShardingTableRuleStatementConverter.convert(segments);
+        rule.getShardingRuleChecker().checkToBeAddedDataNodes(ShardingTableRuleStatementConverter.convertDataNodes(sqlStatement.getRules()), false);
+    }
+    
+    @Override
+    public ShardingRuleConfiguration buildToBeCreatedRuleConfiguration(final CreateShardingTableRuleStatement sqlStatement) {
+        return ShardingTableRuleStatementConverter.convert(sqlStatement.getRules());
     }
     
     private Collection<String> getDuplicatedRuleNames(final CreateShardingTableRuleStatement sqlStatement) {
@@ -65,7 +73,7 @@ private Collection<String> getDuplicatedRuleNames(final CreateShardingTableRuleS
     }
     
     private Collection<String> getCurrentShardingTables() {
-        Collection<String> result = new LinkedList<>();
+        Collection<String> result = new CaseInsensitiveSet<>();
         result.addAll(rule.getConfiguration().getTables().stream().map(ShardingTableRuleConfiguration::getLogicTable).collect(Collectors.toSet()));
         result.addAll(rule.getConfiguration().getAutoTables().stream().map(ShardingAutoTableRuleConfiguration::getLogicTable).collect(Collectors.toSet()));
         return result;