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 c87252791f8 Move encrypt table name uniqueness checks to Bean 
Validation (#40054)
c87252791f8 is described below

commit c87252791f88992aabe04ab0bfe76876629219cb
Author: Liang Zhang <[email protected]>
AuthorDate: Sun Oct 4 22:55:58 2026 +0800

    Move encrypt table name uniqueness checks to Bean Validation (#40054)
    
    * Align DistSQL executor tests with production SPIs
    
    * Move encrypt table name uniqueness checks to Bean Validation
---
 .../user-manual/error-code/sql-error-code.cn.md    |   1 -
 .../user-manual/error-code/sql-error-code.en.md    |   1 -
 .../encrypt/config/EncryptRuleConfiguration.java   |   2 +
 .../config/EncryptRuleConfigurationTest.java       |  15 +++
 .../config/EncryptRuleConfigurationChecker.java    |  47 ---------
 .../MissingRequiredEncryptColumnException.java     |  34 -------
 ...g.rule.checker.DatabaseRuleConfigurationChecker |  18 ----
 .../EncryptRuleConfigurationCheckerTest.java       |  51 ----------
 .../constraint/unique/UniqueTableNames.java        |  59 +++++++++++
 .../unique/UniqueTableNamesValidator.java          |  53 ++++++++++
 .../unique/UniqueTableNamesValidatorTest.java      | 108 +++++++++++++++++++++
 .../spi}/fixture/FixtureRule.java                  |   2 +-
 .../fixture/FixtureDistSQLQueryExecutor.java       |   2 +-
 .../fixture/FixtureDistSQLQueryStatement.java      |   2 +-
 .../fixture/FixtureDistSQLUpdateExecutor.java      |   2 +-
 .../fixture/FixtureDistSQLUpdateStatement.java     |   2 +-
 .../FixtureDatabaseRuleDefinitionExecutor.java     |   2 +-
 .../FixtureDatabaseRuleDefinitionStatement.java    |   2 +-
 ...handler.executor.spi.query.DistSQLQueryExecutor |   2 +-
 ...ndler.executor.spi.update.DistSQLUpdateExecutor |   2 +-
 ...te.rule.database.DatabaseRuleDefinitionExecutor |   2 +-
 .../reachability-metadata.json                     |  36 +++++++
 22 files changed, 283 insertions(+), 162 deletions(-)

diff --git a/docs/document/content/user-manual/error-code/sql-error-code.cn.md 
b/docs/document/content/user-manual/error-code/sql-error-code.cn.md
index 8957f63747b..aa93f19a1fd 100644
--- a/docs/document/content/user-manual/error-code/sql-error-code.cn.md
+++ b/docs/document/content/user-manual/error-code/sql-error-code.cn.md
@@ -230,7 +230,6 @@ SQL 错误码以标准的 SQL State,Vendor Code 和详细错误信息提供,
 
 | Vendor Code | SQL State | 错误信息                                               
                                          |
 
|-------------|-----------|----------------------------------------------------------------------------------------------|
-| 21000       | 42S02     | %s column is required in %s.                       
                                          |
 | 21001       | 42S02     | Can not find encrypt table '%s'.                   
                                          |
 | 21002       | 42S02     | Can not find logic encrypt column by '%s'.         
                                          |
 | 21003       | 42S02     | Can not find encrypt column '%s' from table '%s'.  
                                          |
diff --git a/docs/document/content/user-manual/error-code/sql-error-code.en.md 
b/docs/document/content/user-manual/error-code/sql-error-code.en.md
index 7fbf94c6617..e8d16b584da 100644
--- a/docs/document/content/user-manual/error-code/sql-error-code.en.md
+++ b/docs/document/content/user-manual/error-code/sql-error-code.en.md
@@ -230,7 +230,6 @@ SQL error codes provide by standard `SQL State`, `Vendor 
Code` and `Reason`, whi
 
 | Vendor Code | SQL State | Reason                                             
                                          |
 
|-------------|-----------|----------------------------------------------------------------------------------------------|
-| 21000       | 42S02     | %s column is required in %s.                       
                                          |
 | 21001       | 42S02     | Can not find encrypt table '%s'.                   
                                          |
 | 21002       | 42S02     | Can not find logic encrypt column by '%s'.         
                                          |
 | 21003       | 42S02     | Can not find encrypt column '%s' from table '%s'.  
                                          |
diff --git 
a/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfiguration.java
 
b/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfiguration.java
index f25aaabdac8..af880959b8e 100644
--- 
a/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfiguration.java
+++ 
b/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfiguration.java
@@ -26,6 +26,7 @@ import 
org.apache.shardingsphere.infra.config.rule.function.EnhancedRuleConfigur
 import 
org.apache.shardingsphere.infra.config.rule.scope.DatabaseRuleConfiguration;
 import 
org.apache.shardingsphere.infra.config.rule.validator.constraint.reference.ConfigurationReferenceExists;
 import 
org.apache.shardingsphere.infra.config.rule.validator.constraint.spi.SPITypeExists;
+import 
org.apache.shardingsphere.infra.config.rule.validator.constraint.unique.UniqueTableNames;
 import 
org.apache.shardingsphere.infra.config.rule.validator.group.RuleConfigurationTypeValidationGroup;
 
 import javax.validation.Valid;
@@ -46,6 +47,7 @@ public final class EncryptRuleConfiguration implements 
DatabaseRuleConfiguration
     
     @NotNull
     @Valid
+    @UniqueTableNames(groups = RuleConfigurationTypeValidationGroup.class)
     private final Collection<@NotNull EncryptTableRuleConfiguration> tables;
     
     @NotNull
diff --git 
a/features/encrypt/api/src/test/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfigurationTest.java
 
b/features/encrypt/api/src/test/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfigurationTest.java
index ae8a651dc51..e295c92d717 100644
--- 
a/features/encrypt/api/src/test/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfigurationTest.java
+++ 
b/features/encrypt/api/src/test/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfigurationTest.java
@@ -28,6 +28,7 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
+import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -65,6 +66,8 @@ class EncryptRuleConfigurationTest {
                 Arguments.of("Empty configuration", new 
EncryptRuleConfiguration(Collections.emptyList(), Collections.emptyMap())),
                 Arguments.of("Table without columns", new 
EncryptRuleConfiguration(
                         Collections.singleton(new 
EncryptTableRuleConfiguration("foo_tbl", Collections.emptyList())), 
Collections.emptyMap())),
+                Arguments.of("Table names remain case sensitive", new 
EncryptRuleConfiguration(Arrays.asList(
+                        new EncryptTableRuleConfiguration("foo_tbl", 
Collections.emptyList()), new EncryptTableRuleConfiguration("FOO_TBL", 
Collections.emptyList())), Collections.emptyMap())),
                 Arguments.of("Complete configuration", 
createValidRuleConfiguration()),
                 Arguments.of("Multiple columns without derived column 
conflicts", createValidMultipleColumnRuleConfiguration()));
     }
@@ -101,6 +104,18 @@ class EncryptRuleConfigurationTest {
         assertThat(actual.getMessage(), is("Invalid 'EncryptRuleConfiguration' 
rule, error message is: Property `tables` references unconfigured encryptors 
`bar_encryptor`."));
     }
     
+    @Test
+    void assertDuplicateTableNamesViolation() {
+        EncryptRuleConfiguration ruleConfig = new 
EncryptRuleConfiguration(Arrays.asList(new 
EncryptTableRuleConfiguration("foo_tbl", Collections.emptyList()),
+                new EncryptTableRuleConfiguration("bar_tbl", 
Collections.emptyList()), new EncryptTableRuleConfiguration("foo_tbl", 
Collections.emptyList())), Collections.emptyMap());
+        InvalidRuleConfigurationException actual = 
assertThrows(InvalidRuleConfigurationException.class, () -> 
RuleConfigurationValidator.validate(ruleConfig));
+        assertThat(actual.getMessage(), is("Invalid 'EncryptRuleConfiguration' 
rule, error message is: Property `tables` must not contain duplicate table 
names `foo_tbl`."));
+        SQLException actualSQLException = actual.toSQLException();
+        assertThat(actualSQLException.getMessage(), is(actual.getMessage()));
+        assertThat(actualSQLException.getSQLState(), is("44000"));
+        assertThat(actualSQLException.getErrorCode(), is(10200));
+    }
+    
     private static Stream<Arguments> invalidRuleConfigurationArguments() {
         EncryptColumnRuleConfiguration invalidAssistedQueryName = 
createValidColumnConfiguration();
         invalidAssistedQueryName.setAssistedQuery(new 
EncryptColumnItemRuleConfiguration("", "foo_encryptor"));
diff --git 
a/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/checker/config/EncryptRuleConfigurationChecker.java
 
b/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/checker/config/EncryptRuleConfigurationChecker.java
deleted file mode 100644
index 429796130be..00000000000
--- 
a/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/checker/config/EncryptRuleConfigurationChecker.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.shardingsphere.encrypt.checker.config;
-
-import org.apache.shardingsphere.encrypt.config.EncryptRuleConfiguration;
-import 
org.apache.shardingsphere.encrypt.config.rule.EncryptTableRuleConfiguration;
-import org.apache.shardingsphere.encrypt.constant.EncryptOrder;
-import 
org.apache.shardingsphere.infra.config.rule.checker.DatabaseRuleConfigurationChecker;
-
-import java.util.Collection;
-import java.util.stream.Collectors;
-
-/**
- * Encrypt rule configuration checker.
- */
-public final class EncryptRuleConfigurationChecker implements 
DatabaseRuleConfigurationChecker<EncryptRuleConfiguration> {
-    
-    @Override
-    public Collection<String> getTableNames(final EncryptRuleConfiguration 
ruleConfig) {
-        return 
ruleConfig.getTables().stream().map(EncryptTableRuleConfiguration::getName).collect(Collectors.toList());
-    }
-    
-    @Override
-    public int getOrder() {
-        return EncryptOrder.ORDER;
-    }
-    
-    @Override
-    public Class<EncryptRuleConfiguration> getTypeClass() {
-        return EncryptRuleConfiguration.class;
-    }
-}
diff --git 
a/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/exception/metadata/MissingRequiredEncryptColumnException.java
 
b/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/exception/metadata/MissingRequiredEncryptColumnException.java
deleted file mode 100644
index ddc65468d4f..00000000000
--- 
a/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/exception/metadata/MissingRequiredEncryptColumnException.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.shardingsphere.encrypt.exception.metadata;
-
-import org.apache.shardingsphere.encrypt.exception.EncryptSQLException;
-import 
org.apache.shardingsphere.infra.exception.external.sql.identifier.SQLExceptionIdentifier;
-import 
org.apache.shardingsphere.infra.exception.external.sql.sqlstate.XOpenSQLState;
-
-/**
- * Missing required encrypt column Exception.
- */
-public final class MissingRequiredEncryptColumnException extends 
EncryptSQLException {
-    
-    private static final long serialVersionUID = -6765795304282762539L;
-    
-    public MissingRequiredEncryptColumnException(final String columnType, 
final SQLExceptionIdentifier sqlExceptionIdentifier) {
-        super(XOpenSQLState.NOT_FOUND, 0, "%s column is required in %s.", 
columnType, sqlExceptionIdentifier);
-    }
-}
diff --git 
a/features/encrypt/core/src/main/resources/META-INF/services/org.apache.shardingsphere.infra.config.rule.checker.DatabaseRuleConfigurationChecker
 
b/features/encrypt/core/src/main/resources/META-INF/services/org.apache.shardingsphere.infra.config.rule.checker.DatabaseRuleConfigurationChecker
deleted file mode 100644
index c02e5b307d8..00000000000
--- 
a/features/encrypt/core/src/main/resources/META-INF/services/org.apache.shardingsphere.infra.config.rule.checker.DatabaseRuleConfigurationChecker
+++ /dev/null
@@ -1,18 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-org.apache.shardingsphere.encrypt.checker.config.EncryptRuleConfigurationChecker
diff --git 
a/features/encrypt/core/src/test/java/org/apache/shardingsphere/encrypt/checker/config/EncryptRuleConfigurationCheckerTest.java
 
b/features/encrypt/core/src/test/java/org/apache/shardingsphere/encrypt/checker/config/EncryptRuleConfigurationCheckerTest.java
deleted file mode 100644
index 879e1b47401..00000000000
--- 
a/features/encrypt/core/src/test/java/org/apache/shardingsphere/encrypt/checker/config/EncryptRuleConfigurationCheckerTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.shardingsphere.encrypt.checker.config;
-
-import org.apache.shardingsphere.encrypt.config.EncryptRuleConfiguration;
-import 
org.apache.shardingsphere.encrypt.config.rule.EncryptTableRuleConfiguration;
-import 
org.apache.shardingsphere.infra.config.rule.checker.DatabaseRuleConfigurationChecker;
-import org.apache.shardingsphere.infra.spi.type.ordered.OrderedSPILoader;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.util.Collections;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-class EncryptRuleConfigurationCheckerTest {
-    
-    @SuppressWarnings("rawtypes")
-    private DatabaseRuleConfigurationChecker checker;
-    
-    @BeforeEach
-    void setUp() {
-        checker = 
OrderedSPILoader.getServicesByClass(DatabaseRuleConfigurationChecker.class, 
Collections.singleton(EncryptRuleConfiguration.class)).get(EncryptRuleConfiguration.class);
-    }
-    
-    @SuppressWarnings("unchecked")
-    @Test
-    void assertGetTableNames() {
-        assertThat(checker.getTableNames(createValidRuleConfiguration()), 
is(Collections.singletonList("t_encrypt")));
-    }
-    
-    private EncryptRuleConfiguration createValidRuleConfiguration() {
-        return new EncryptRuleConfiguration(Collections.singleton(new 
EncryptTableRuleConfiguration("t_encrypt", Collections.emptyList())), 
Collections.emptyMap());
-    }
-}
diff --git 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/unique/UniqueTableNames.java
 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/unique/UniqueTableNames.java
new file mode 100644
index 00000000000..140ab1356bc
--- /dev/null
+++ 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/unique/UniqueTableNames.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 
org.apache.shardingsphere.infra.config.rule.validator.constraint.unique;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Unique table names constraint.
+ * Collection elements must expose a public {@code String getName()} method.
+ * Apply after null collection and element constraints have passed.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = UniqueTableNamesValidator.class)
+@Documented
+public @interface UniqueTableNames {
+    
+    /**
+     * Get message.
+     *
+     * @return message
+     */
+    String message() default "must not contain duplicate table names";
+    
+    /**
+     * Get groups.
+     *
+     * @return groups
+     */
+    Class<?>[] groups() default {};
+    
+    /**
+     * Get payload.
+     *
+     * @return payload
+     */
+    Class<? extends Payload>[] payload() default {};
+}
diff --git 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/unique/UniqueTableNamesValidator.java
 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/unique/UniqueTableNamesValidator.java
new file mode 100644
index 00000000000..283b1ff39d5
--- /dev/null
+++ 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/unique/UniqueTableNamesValidator.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 
org.apache.shardingsphere.infra.config.rule.validator.constraint.unique;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import javax.validation.ValidationException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Unique table names validator.
+ */
+public final class UniqueTableNamesValidator implements 
ConstraintValidator<UniqueTableNames, Collection<?>> {
+    
+    @Override
+    public boolean isValid(final Collection<?> value, final 
ConstraintValidatorContext context) {
+        Set<String> tableNames = new HashSet<>(value.size(), 1F);
+        Set<String> duplicatedTableNames = 
value.stream().map(this::getTableName).filter(tableName -> 
!tableNames.add(tableName)).collect(Collectors.toCollection(LinkedHashSet::new));
+        if (duplicatedTableNames.isEmpty()) {
+            return true;
+        }
+        context.disableDefaultConstraintViolation();
+        context.buildConstraintViolationWithTemplate(String.format("must not 
contain duplicate table names `%s`", String.join(", ", 
duplicatedTableNames))).addConstraintViolation();
+        return false;
+    }
+    
+    private String getTableName(final Object tableConfig) {
+        try {
+            return (String) 
tableConfig.getClass().getMethod("getName").invoke(tableConfig);
+        } catch (final ReflectiveOperationException ex) {
+            throw new ValidationException(String.format("Can not read property 
`name` from `%s`.", tableConfig.getClass().getName()), ex);
+        }
+    }
+}
diff --git 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/unique/UniqueTableNamesValidatorTest.java
 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/unique/UniqueTableNamesValidatorTest.java
new file mode 100644
index 00000000000..09fdbf63435
--- /dev/null
+++ 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/unique/UniqueTableNamesValidatorTest.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 
org.apache.shardingsphere.infra.config.rule.validator.constraint.unique;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+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 javax.validation.ConstraintValidatorContext;
+import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder;
+import javax.validation.ValidationException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+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.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.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+class UniqueTableNamesValidatorTest {
+    
+    private final UniqueTableNamesValidator validator = new 
UniqueTableNamesValidator();
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("validTablesArguments")
+    void assertIsValidWithoutDuplicatedNames(final String name, final 
Collection<?> tables) {
+        ConstraintValidatorContext context = 
mock(ConstraintValidatorContext.class);
+        assertTrue(validator.isValid(tables, context));
+        verifyNoInteractions(context);
+    }
+    
+    private static Stream<Arguments> validTablesArguments() {
+        return Stream.of(
+                Arguments.of("Empty tables", Collections.emptyList()),
+                Arguments.of("Single table", createTables("foo_tbl")),
+                Arguments.of("Distinct table names", createTables("foo_tbl", 
"bar_tbl")),
+                Arguments.of("Table names remain case sensitive", 
createTables("foo_tbl", "FOO_TBL")));
+    }
+    
+    private static Collection<Table> createTables(final String... tableNames) {
+        return 
Arrays.stream(tableNames).map(Table::new).collect(Collectors.toList());
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("duplicatedTablesArguments")
+    void assertIsValidWithDuplicatedNames(final String name, final 
Collection<?> tables, final String expectedMessage) {
+        ConstraintValidatorContext context = 
mock(ConstraintValidatorContext.class);
+        ConstraintViolationBuilder builder = 
mock(ConstraintViolationBuilder.class);
+        
when(context.buildConstraintViolationWithTemplate(expectedMessage)).thenReturn(builder);
+        assertFalse(validator.isValid(tables, context));
+        verify(context).disableDefaultConstraintViolation();
+        verify(builder).addConstraintViolation();
+    }
+    
+    private static Stream<Arguments> duplicatedTablesArguments() {
+        return Stream.of(
+                Arguments.of("Nonadjacent duplicated name", 
createTables("foo_tbl", "bar_tbl", "foo_tbl"), "must not contain duplicate 
table names `foo_tbl`"),
+                Arguments.of("All duplicated names retain encounter order", 
createTables("z_tbl", "a_tbl", "z_tbl", "a_tbl", "z_tbl"),
+                        "must not contain duplicate table names `z_tbl, 
a_tbl`"),
+                Arguments.of("Names read across different table types", 
Arrays.asList(new Table("foo_tbl"), new OtherTable("foo_tbl")), "must not 
contain duplicate table names `foo_tbl`"));
+    }
+    
+    @Test
+    void assertIsValidWithMissingNameProperty() {
+        ValidationException actual = assertThrows(ValidationException.class, 
() -> validator.isValid(Collections.singleton(new Object()), 
mock(ConstraintValidatorContext.class)));
+        assertThat(actual.getMessage(), is("Can not read property `name` from 
`java.lang.Object`."));
+    }
+    
+    @RequiredArgsConstructor
+    @Getter
+    public static final class Table {
+        
+        private final String name;
+    }
+    
+    @RequiredArgsConstructor
+    @Getter
+    public static final class OtherTable {
+        
+        private final String name;
+    }
+}
diff --git 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureRule.java
 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/fixture/FixtureRule.java
similarity index 94%
rename from 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureRule.java
rename to 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/fixture/FixtureRule.java
index 5a7a8dc5818..14aac497e45 100644
--- 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureRule.java
+++ 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/fixture/FixtureRule.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.distsql.handler.engine.concurrent.fixture;
+package org.apache.shardingsphere.distsql.handler.executor.spi.fixture;
 
 import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
diff --git 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLQueryExecutor.java
 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/query/fixture/FixtureDistSQLQueryExecutor.java
similarity index 97%
rename from 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLQueryExecutor.java
rename to 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/query/fixture/FixtureDistSQLQueryExecutor.java
index f2cddad4efd..5e26b380e55 100644
--- 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLQueryExecutor.java
+++ 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/query/fixture/FixtureDistSQLQueryExecutor.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.distsql.handler.engine.concurrent.fixture;
+package org.apache.shardingsphere.distsql.handler.executor.spi.query.fixture;
 
 import 
org.apache.shardingsphere.distsql.handler.executor.aware.DistSQLExecutorRuleAware;
 import 
org.apache.shardingsphere.distsql.handler.executor.spi.query.DistSQLQueryExecutor;
diff --git 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLQueryStatement.java
 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/query/fixture/FixtureDistSQLQueryStatement.java
similarity index 93%
rename from 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLQueryStatement.java
rename to 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/query/fixture/FixtureDistSQLQueryStatement.java
index bdf9825d462..db3ac2f9df6 100644
--- 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLQueryStatement.java
+++ 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/query/fixture/FixtureDistSQLQueryStatement.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.distsql.handler.engine.concurrent.fixture;
+package org.apache.shardingsphere.distsql.handler.executor.spi.query.fixture;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;
diff --git 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLUpdateExecutor.java
 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/fixture/FixtureDistSQLUpdateExecutor.java
similarity index 96%
rename from 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLUpdateExecutor.java
rename to 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/fixture/FixtureDistSQLUpdateExecutor.java
index f1c58859811..25031bf0656 100644
--- 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLUpdateExecutor.java
+++ 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/fixture/FixtureDistSQLUpdateExecutor.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.distsql.handler.engine.concurrent.fixture;
+package org.apache.shardingsphere.distsql.handler.executor.spi.update.fixture;
 
 import 
org.apache.shardingsphere.distsql.handler.executor.aware.DistSQLExecutorRuleAware;
 import 
org.apache.shardingsphere.distsql.handler.executor.spi.update.DistSQLUpdateExecutor;
diff --git 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLUpdateStatement.java
 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/fixture/FixtureDistSQLUpdateStatement.java
similarity index 93%
rename from 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLUpdateStatement.java
rename to 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/fixture/FixtureDistSQLUpdateStatement.java
index edb69a65e9a..ed8e4f6440e 100644
--- 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDistSQLUpdateStatement.java
+++ 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/fixture/FixtureDistSQLUpdateStatement.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.distsql.handler.engine.concurrent.fixture;
+package org.apache.shardingsphere.distsql.handler.executor.spi.update.fixture;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;
diff --git 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDatabaseRuleDefinitionExecutor.java
 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/rule/database/fixture/FixtureDatabaseRuleDefinitionExecutor.java
similarity index 95%
rename from 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDatabaseRuleDefinitionExecutor.java
rename to 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/rule/database/fixture/FixtureDatabaseRuleDefinitionExecutor.java
index 6695db18be9..aa17866d2d0 100644
--- 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDatabaseRuleDefinitionExecutor.java
+++ 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/rule/database/fixture/FixtureDatabaseRuleDefinitionExecutor.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.distsql.handler.engine.concurrent.fixture;
+package 
org.apache.shardingsphere.distsql.handler.executor.spi.update.rule.database.fixture;
 
 import 
org.apache.shardingsphere.distsql.handler.executor.spi.update.rule.database.DatabaseRuleDefinitionExecutor;
 import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
diff --git 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDatabaseRuleDefinitionStatement.java
 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/rule/database/fixture/FixtureDatabaseRuleDefinitionStatement.java
similarity index 93%
rename from 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDatabaseRuleDefinitionStatement.java
rename to 
infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/rule/database/fixture/FixtureDatabaseRuleDefinitionStatement.java
index f251a1896d5..8eae6405b6e 100644
--- 
a/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/engine/concurrent/fixture/FixtureDatabaseRuleDefinitionStatement.java
+++ 
b/infra/distsql-handler/src/test/java/org/apache/shardingsphere/distsql/handler/executor/spi/update/rule/database/fixture/FixtureDatabaseRuleDefinitionStatement.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.distsql.handler.engine.concurrent.fixture;
+package 
org.apache.shardingsphere.distsql.handler.executor.spi.update.rule.database.fixture;
 
 import lombok.Getter;
 import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
diff --git 
a/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.query.DistSQLQueryExecutor
 
b/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.query.DistSQLQueryExecutor
index 00e99159f18..a37bcf3ca6c 100644
--- 
a/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.query.DistSQLQueryExecutor
+++ 
b/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.query.DistSQLQueryExecutor
@@ -15,4 +15,4 @@
 # limitations under the License.
 #
 
-org.apache.shardingsphere.distsql.handler.engine.concurrent.fixture.FixtureDistSQLQueryExecutor
+org.apache.shardingsphere.distsql.handler.executor.spi.query.fixture.FixtureDistSQLQueryExecutor
diff --git 
a/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.update.DistSQLUpdateExecutor
 
b/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.update.DistSQLUpdateExecutor
index eb012944bf2..1fc8633dbe3 100644
--- 
a/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.update.DistSQLUpdateExecutor
+++ 
b/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.update.DistSQLUpdateExecutor
@@ -15,4 +15,4 @@
 # limitations under the License.
 #
 
-org.apache.shardingsphere.distsql.handler.engine.concurrent.fixture.FixtureDistSQLUpdateExecutor
+org.apache.shardingsphere.distsql.handler.executor.spi.update.fixture.FixtureDistSQLUpdateExecutor
diff --git 
a/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.update.rule.database.DatabaseRuleDefinitionExecutor
 
b/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.update.rule.database.DatabaseRuleDefinitionExecutor
index bc91e04442b..fc203c14ea7 100644
--- 
a/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.update.rule.database.DatabaseRuleDefinitionExecutor
+++ 
b/infra/distsql-handler/src/test/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.executor.spi.update.rule.database.DatabaseRuleDefinitionExecutor
@@ -15,4 +15,4 @@
 # limitations under the License.
 #
 
-org.apache.shardingsphere.distsql.handler.engine.concurrent.fixture.FixtureDatabaseRuleDefinitionExecutor
+org.apache.shardingsphere.distsql.handler.executor.spi.update.rule.database.fixture.FixtureDatabaseRuleDefinitionExecutor
diff --git 
a/infra/reachability-metadata/src/main/resources/META-INF/native-image/org.apache.shardingsphere/infra-reachability-metadata/reachability-metadata.json
 
b/infra/reachability-metadata/src/main/resources/META-INF/native-image/org.apache.shardingsphere/infra-reachability-metadata/reachability-metadata.json
index 316602b95ab..cbc131c4a6e 100644
--- 
a/infra/reachability-metadata/src/main/resources/META-INF/native-image/org.apache.shardingsphere/infra-reachability-metadata/reachability-metadata.json
+++ 
b/infra/reachability-metadata/src/main/resources/META-INF/native-image/org.apache.shardingsphere/infra-reachability-metadata/reachability-metadata.json
@@ -337,6 +337,38 @@
         }
       ]
     },
+    {
+      "condition": {
+        "typeReached": 
"org.apache.shardingsphere.infra.config.rule.validator.RuleConfigurationValidator"
+      },
+      "type": 
"org.apache.shardingsphere.infra.config.rule.validator.constraint.unique.UniqueTableNamesValidator",
+      "methods": [
+        {
+          "name": "<init>",
+          "parameterTypes": []
+        }
+      ]
+    },
+    {
+      "condition": {
+        "typeReached": 
"org.apache.shardingsphere.infra.config.rule.validator.RuleConfigurationValidator"
+      },
+      "type": 
"org.apache.shardingsphere.infra.config.rule.validator.constraint.unique.UniqueTableNames",
+      "methods": [
+        {
+          "name": "groups",
+          "parameterTypes": []
+        },
+        {
+          "name": "message",
+          "parameterTypes": []
+        },
+        {
+          "name": "payload",
+          "parameterTypes": []
+        }
+      ]
+    },
     {
       "condition": {
         "typeReached": 
"org.apache.shardingsphere.infra.config.rule.validator.RuleConfigurationValidator"
@@ -444,6 +476,10 @@
         {
           "name": "getColumns",
           "parameterTypes": []
+        },
+        {
+          "name": "getName",
+          "parameterTypes": []
         }
       ]
     },

Reply via email to