You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by me...@apache.org on 2026/03/20 06:30:40 UTC

(shardingsphere) branch master updated: Supports loading single tables with case-insensitive names (#38524)

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

menghaoran 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 59ea12e0d02 Supports loading single tables with case-insensitive names (#38524)
59ea12e0d02 is described below

commit 59ea12e0d02b889e8e8f8f13d880c64e2264914c
Author: Haoran Meng <me...@gmail.com>
AuthorDate: Fri Mar 20 14:30:34 2026 +0800

    Supports loading single tables with case-insensitive names (#38524)
---
 .../data/loader/type/SchemaMetaDataLoader.java     |  8 +--
 .../data/loader/type/SchemaMetaDataLoaderTest.java | 36 ++++++++++-
 .../shardingsphere/infra/datanode/DataNode.java    |  2 +-
 .../infra/datanode/DataNodeTest.java               |  2 +-
 .../single/datanode/SingleTableDataNodeLoader.java | 11 ++--
 .../shardingsphere/single/rule/SingleRule.java     |  2 +-
 .../attribute/SingleDataNodeRuleAttribute.java     | 11 +++-
 .../SingleMutableDataNodeRuleAttribute.java        | 72 +++++++++++++++++-----
 .../attribute/SingleTableMapperRuleAttribute.java  |  4 +-
 .../single/util/SingleTableLoadUtils.java          |  3 +-
 .../datanode/SingleTableDataNodeLoaderTest.java    |  9 +++
 .../single/route/engine/SingleRouteEngineTest.java |  6 +-
 .../SingleMutableDataNodeRuleAttributeTest.java    | 13 ++++
 .../SingleTableMapperRuleAttributeTest.java        |  9 +++
 .../single/util/SingleTableLoadUtilsTest.java      |  3 +-
 15 files changed, 151 insertions(+), 40 deletions(-)

diff --git a/database/connector/core/src/main/java/org/apache/shardingsphere/database/connector/core/metadata/data/loader/type/SchemaMetaDataLoader.java b/database/connector/core/src/main/java/org/apache/shardingsphere/database/connector/core/metadata/data/loader/type/SchemaMetaDataLoader.java
index f7f39abc5be..099e16e1fa2 100644
--- a/database/connector/core/src/main/java/org/apache/shardingsphere/database/connector/core/metadata/data/loader/type/SchemaMetaDataLoader.java
+++ b/database/connector/core/src/main/java/org/apache/shardingsphere/database/connector/core/metadata/data/loader/type/SchemaMetaDataLoader.java
@@ -17,8 +17,6 @@
 
 package org.apache.shardingsphere.database.connector.core.metadata.data.loader.type;
 
-import com.cedarsoftware.util.CaseInsensitiveMap;
-import com.cedarsoftware.util.CaseInsensitiveSet;
 import org.apache.shardingsphere.database.connector.core.metadata.data.loader.MetaDataLoaderConnection;
 import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
 import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.schema.DialectSchemaOption;
@@ -32,6 +30,8 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.Map;
 
@@ -76,7 +76,7 @@ public final class SchemaMetaDataLoader {
     public Map<String, Collection<String>> loadSchemaTableNames(final String databaseName, final DataSource dataSource, final Collection<String> excludedTables) throws SQLException {
         try (MetaDataLoaderConnection connection = new MetaDataLoaderConnection(databaseType, dataSource.getConnection())) {
             Collection<String> schemaNames = loadSchemaNames(connection);
-            Map<String, Collection<String>> result = new CaseInsensitiveMap<>(schemaNames.size(), 1F);
+            Map<String, Collection<String>> result = new LinkedHashMap<>(schemaNames.size(), 1F);
             for (String each : schemaNames) {
                 String schemaName = schemaOption.getDefaultSchema().isPresent() ? each : databaseName;
                 result.put(schemaName, loadTableNames(connection, each, excludedTables));
@@ -110,7 +110,7 @@ public final class SchemaMetaDataLoader {
     }
     
     private Collection<String> loadTableNames(final Connection connection, final String schemaName, final Collection<String> excludedTables) throws SQLException {
-        Collection<String> result = new CaseInsensitiveSet<>();
+        Collection<String> result = new LinkedHashSet<>();
         String[] tableTypes = new String[]{TABLE_TYPE, PARTITIONED_TABLE_TYPE, VIEW_TYPE, SYSTEM_TABLE_TYPE, SYSTEM_VIEW_TYPE};
         try (ResultSet resultSet = connection.getMetaData().getTables(connection.getCatalog(), schemaName, null, tableTypes)) {
             while (resultSet.next()) {
diff --git a/database/connector/core/src/test/java/org/apache/shardingsphere/database/connector/core/metadata/data/loader/type/SchemaMetaDataLoaderTest.java b/database/connector/core/src/test/java/org/apache/shardingsphere/database/connector/core/metadata/data/loader/type/SchemaMetaDataLoaderTest.java
index f14e2ff1b84..a89ba8ef5aa 100644
--- a/database/connector/core/src/test/java/org/apache/shardingsphere/database/connector/core/metadata/data/loader/type/SchemaMetaDataLoaderTest.java
+++ b/database/connector/core/src/test/java/org/apache/shardingsphere/database/connector/core/metadata/data/loader/type/SchemaMetaDataLoaderTest.java
@@ -17,7 +17,6 @@
 
 package org.apache.shardingsphere.database.connector.core.metadata.data.loader.type;
 
-import com.cedarsoftware.util.CaseInsensitiveSet;
 import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
 import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.schema.DialectSchemaOption;
 import org.apache.shardingsphere.database.connector.core.metadata.database.system.DialectSystemDatabase;
@@ -36,7 +35,9 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Collection;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Optional;
 
@@ -80,7 +81,7 @@ class SchemaMetaDataLoaderTest {
                 when(connection.getMetaData().getTables("catalog", "public", null, TABLE_TYPES)).thenReturn(tableResultSet);
                 Map<String, Collection<String>> actual = new SchemaMetaDataLoader(databaseType)
                         .loadSchemaTableNames("logic_db", dataSourceWithoutDefaultSchema, Collections.singleton("excluded_tbl"));
-                Map<String, Collection<String>> expected = Collections.singletonMap("logic_db", new CaseInsensitiveSet<>(Collections.singleton("tbl")));
+                Map<String, Collection<String>> expected = Collections.singletonMap("logic_db", new LinkedHashSet<>(Collections.singleton("tbl")));
                 assertThat(actual, is(expected));
             }
         }
@@ -110,12 +111,41 @@ class SchemaMetaDataLoaderTest {
                 when(tableResultSet.getString("TABLE_NAME")).thenReturn("tbl_visible");
                 when(connection.getMetaData().getTables("catalog_2", "user_schema", null, TABLE_TYPES)).thenReturn(tableResultSet);
                 Map<String, Collection<String>> actual = new SchemaMetaDataLoader(databaseType).loadSchemaTableNames("logic_db_2", dataSourceWithDefaultSchema, Collections.emptyList());
-                Map<String, Collection<String>> expected = Collections.singletonMap("user_schema", new CaseInsensitiveSet<>(Collections.singleton("tbl_visible")));
+                Map<String, Collection<String>> expected = Collections.singletonMap("user_schema", new LinkedHashSet<>(Collections.singleton("tbl_visible")));
                 assertThat(actual, is(expected));
             }
         }
     }
     
+    @Test
+    void assertLoadSchemaTableNamesPreservesCaseSensitiveTableNames() throws SQLException {
+        DialectSchemaOption schemaOption = mock(DialectSchemaOption.class);
+        when(schemaOption.getDefaultSchema()).thenReturn(Optional.of("public"));
+        DialectDatabaseMetaData dialectDatabaseMetaData = mock(DialectDatabaseMetaData.class);
+        when(dialectDatabaseMetaData.getSchemaOption()).thenReturn(schemaOption);
+        DialectSystemDatabase dialectSystemDatabase = mock(DialectSystemDatabase.class);
+        when(dialectSystemDatabase.getSystemSchemas()).thenReturn(Collections.emptySet());
+        try (MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
+            databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.getService(DialectDatabaseMetaData.class, databaseType)).thenReturn(dialectDatabaseMetaData);
+            databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectSystemDatabase.class, databaseType)).thenReturn(Optional.of(dialectSystemDatabase));
+            try (MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class)) {
+                typedSPILoader.when(() -> TypedSPILoader.getService(DialectDatabaseMetaData.class, null)).thenReturn(dialectDatabaseMetaData);
+                Connection connection = dataSourceWithDefaultSchema.getConnection();
+                when(connection.getCatalog()).thenReturn("catalog_3");
+                ResultSet schemaResultSet = mock(ResultSet.class);
+                when(schemaResultSet.next()).thenReturn(true, false);
+                when(schemaResultSet.getString("TABLE_SCHEM")).thenReturn("public");
+                when(connection.getMetaData().getSchemas()).thenReturn(schemaResultSet);
+                ResultSet tableResultSet = mock(ResultSet.class);
+                when(tableResultSet.next()).thenReturn(true, true, false);
+                when(tableResultSet.getString("TABLE_NAME")).thenReturn("Test3", "test3");
+                when(connection.getMetaData().getTables("catalog_3", "public", null, TABLE_TYPES)).thenReturn(tableResultSet);
+                Map<String, Collection<String>> actual = new SchemaMetaDataLoader(databaseType).loadSchemaTableNames("logic_db_3", dataSourceWithDefaultSchema, Collections.emptyList());
+                assertThat(actual.get("public"), is(new LinkedHashSet<>(Arrays.asList("Test3", "test3"))));
+            }
+        }
+    }
+    
     @Test
     void assertLoadSchemaNames() throws SQLException {
         DialectSchemaOption schemaOption = mock(DialectSchemaOption.class);
diff --git a/infra/common/src/main/java/org/apache/shardingsphere/infra/datanode/DataNode.java b/infra/common/src/main/java/org/apache/shardingsphere/infra/datanode/DataNode.java
index 034af2eb099..e8b64705998 100644
--- a/infra/common/src/main/java/org/apache/shardingsphere/infra/datanode/DataNode.java
+++ b/infra/common/src/main/java/org/apache/shardingsphere/infra/datanode/DataNode.java
@@ -78,7 +78,7 @@ public final class DataNode {
         List<String> segments = Splitter.on(DELIMITER).limit(containsSchema ? 3 : 2).splitToList(dataNode);
         dataSourceName = segments.get(0);
         schemaName = getSchemaName(databaseName, dialectDatabaseMetaData, containsSchema, segments);
-        tableName = containsSchema ? segments.get(2).toLowerCase() : segments.get(1).toLowerCase();
+        tableName = containsSchema ? segments.get(2) : segments.get(1);
     }
     
     private String getSchemaName(final String databaseName, final DialectDatabaseMetaData dialectDatabaseMetaData, final boolean containsSchema, final List<String> segments) {
diff --git a/infra/common/src/test/java/org/apache/shardingsphere/infra/datanode/DataNodeTest.java b/infra/common/src/test/java/org/apache/shardingsphere/infra/datanode/DataNodeTest.java
index df10653d8dd..519b0c110c5 100644
--- a/infra/common/src/test/java/org/apache/shardingsphere/infra/datanode/DataNodeTest.java
+++ b/infra/common/src/test/java/org/apache/shardingsphere/infra/datanode/DataNodeTest.java
@@ -138,7 +138,7 @@ class DataNodeTest {
                 Arguments.of("postgresql_without_schema_segment", "test_db", POSTGRESQL_DATABASE_TYPE, "ds.tbl", "ds", "*", "tbl"),
                 Arguments.of("mysql_without_schema_support", "test_db", MYSQL_DATABASE_TYPE, "ds.tbl", "ds", "test_db", "tbl"),
                 Arguments.of("mysql_three_segments_kept_as_table_suffix", "test_db", MYSQL_DATABASE_TYPE, "ds.schema.tbl", "ds", "test_db", "schema.tbl"),
-                Arguments.of("postgresql_lowercases_table", "test_db", POSTGRESQL_DATABASE_TYPE, "ds.schema.TABLE", "ds", "schema", "table"));
+                Arguments.of("postgresql_preserves_table_case", "test_db", POSTGRESQL_DATABASE_TYPE, "ds.schema.TABLE", "ds", "schema", "TABLE"));
     }
     
     private static Stream<Arguments> formatArguments() {
diff --git a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/datanode/SingleTableDataNodeLoader.java b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/datanode/SingleTableDataNodeLoader.java
index 052b4f4ccd7..0983df1d11a 100644
--- a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/datanode/SingleTableDataNodeLoader.java
+++ b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/datanode/SingleTableDataNodeLoader.java
@@ -17,7 +17,6 @@
 
 package org.apache.shardingsphere.single.datanode;
 
-import com.cedarsoftware.util.CaseInsensitiveMap;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import org.apache.shardingsphere.database.connector.core.metadata.data.loader.type.SchemaMetaDataLoader;
@@ -85,14 +84,14 @@ public final class SingleTableDataNodeLoader {
      * @return single table data node map
      */
     public static Map<String, Collection<DataNode>> load(final String databaseName, final Map<String, DataSource> dataSourceMap, final Collection<String> excludedTables) {
-        Map<String, Collection<DataNode>> result = new ConcurrentHashMap<>();
+        Map<String, Collection<DataNode>> result = new LinkedHashMap<>(dataSourceMap.size(), 1F);
         for (Entry<String, DataSource> entry : dataSourceMap.entrySet()) {
             Map<String, Collection<DataNode>> dataNodeMap = load(databaseName, DatabaseTypeEngine.getStorageType(entry.getValue()), entry.getKey(), entry.getValue(), excludedTables);
             for (Entry<String, Collection<DataNode>> each : dataNodeMap.entrySet()) {
                 Collection<DataNode> addedDataNodes = each.getValue();
-                Collection<DataNode> existDataNodes = result.getOrDefault(each.getKey().toLowerCase(), new LinkedHashSet<>(addedDataNodes.size(), 1F));
+                Collection<DataNode> existDataNodes = result.getOrDefault(each.getKey(), new LinkedHashSet<>(addedDataNodes.size(), 1F));
                 existDataNodes.addAll(addedDataNodes);
-                result.putIfAbsent(each.getKey().toLowerCase(), existDataNodes);
+                result.putIfAbsent(each.getKey(), existDataNodes);
             }
         }
         return result;
@@ -101,7 +100,7 @@ public final class SingleTableDataNodeLoader {
     private static Map<String, Collection<DataNode>> load(final String databaseName, final DatabaseType storageType, final String dataSourceName,
                                                           final DataSource dataSource, final Collection<String> excludedTables) {
         Map<String, Collection<String>> schemaTableNames = loadSchemaTableNames(databaseName, storageType, dataSource, dataSourceName, excludedTables);
-        Map<String, Collection<DataNode>> result = new CaseInsensitiveMap<>();
+        Map<String, Collection<DataNode>> result = new LinkedHashMap<>(schemaTableNames.size(), 1F);
         for (Entry<String, Collection<String>> entry : schemaTableNames.entrySet()) {
             for (String each : entry.getValue()) {
                 Collection<DataNode> dataNodes = result.getOrDefault(each, new LinkedList<>());
@@ -144,7 +143,7 @@ public final class SingleTableDataNodeLoader {
             if (null == configuredTablesForSchema) {
                 continue;
             }
-            if (configuredTablesForSchema.contains(SingleTableConstants.ASTERISK) || configuredTablesForSchema.contains(each.getTableName().toLowerCase())) {
+            if (configuredTablesForSchema.contains(SingleTableConstants.ASTERISK) || configuredTablesForSchema.contains(each.getTableName())) {
                 result.add(each);
             }
         }
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 c3fbcfefa71..09a19a0b229 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
@@ -153,7 +153,7 @@ public final class SingleRule implements DatabaseRule {
     public Collection<QualifiedTable> getSingleTables(final Collection<QualifiedTable> qualifiedTables) {
         Collection<QualifiedTable> result = new LinkedList<>();
         for (QualifiedTable each : qualifiedTables) {
-            Collection<DataNode> dataNodes = singleTableDataNodes.getOrDefault(each.getTableName().toLowerCase(), new LinkedList<>());
+            Collection<DataNode> dataNodes = mutableDataNodeRuleAttribute.findTableDataNodes(each.getTableName());
             if (!dataNodes.isEmpty() && containsDataNode(each, dataNodes)) {
                 result.add(each);
             }
diff --git a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleDataNodeRuleAttribute.java b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleDataNodeRuleAttribute.java
index 9451ae702df..85ceefcd6eb 100644
--- a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleDataNodeRuleAttribute.java
+++ b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleDataNodeRuleAttribute.java
@@ -23,6 +23,7 @@ import org.apache.shardingsphere.infra.rule.attribute.datanode.DataNodeRuleAttri
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Map.Entry;
 import java.util.Map;
 import java.util.Optional;
 
@@ -41,7 +42,15 @@ public final class SingleDataNodeRuleAttribute implements DataNodeRuleAttribute
     
     @Override
     public Collection<DataNode> getDataNodesByTableName(final String tableName) {
-        return tableDataNodes.getOrDefault(tableName.toLowerCase(), Collections.emptyList());
+        if (tableDataNodes.containsKey(tableName)) {
+            return tableDataNodes.get(tableName);
+        }
+        for (Entry<String, Collection<DataNode>> entry : tableDataNodes.entrySet()) {
+            if (entry.getKey().equalsIgnoreCase(tableName)) {
+                return entry.getValue();
+            }
+        }
+        return Collections.emptyList();
     }
     
     @Override
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 d0b03eca632..004afaf45c3 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
@@ -18,6 +18,7 @@
 package org.apache.shardingsphere.single.rule.attribute;
 
 import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
 import org.apache.shardingsphere.infra.datanode.DataNode;
@@ -30,9 +31,11 @@ import org.apache.shardingsphere.single.util.SingleTableLoadUtils;
 import javax.sql.DataSource;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Optional;
 
 /**
@@ -55,7 +58,7 @@ public final class SingleMutableDataNodeRuleAttribute implements MutableDataNode
     @Override
     public void put(final String dataSourceName, final String schemaName, final String tableName) {
         if (dataSourceNames.contains(dataSourceName)) {
-            Collection<DataNode> dataNodes = singleTableDataNodes.computeIfAbsent(tableName.toLowerCase(), key -> new LinkedHashSet<>());
+            Collection<DataNode> dataNodes = singleTableDataNodes.computeIfAbsent(tableName, key -> new LinkedHashSet<>());
             dataNodes.add(new DataNode(dataSourceName, schemaName, tableName));
             tableMapperRuleAttribute.getLogicTableNames().add(tableName);
             addTableConfiguration(dataSourceName, schemaName, tableName);
@@ -81,28 +84,30 @@ public final class SingleMutableDataNodeRuleAttribute implements MutableDataNode
     
     @Override
     public void remove(final Collection<String> schemaNames, final String tableName) {
-        if (!singleTableDataNodes.containsKey(tableName.toLowerCase())) {
-            return;
-        }
-        Collection<DataNode> dataNodes = singleTableDataNodes.get(tableName.toLowerCase());
-        Iterator<DataNode> iterator = dataNodes.iterator();
-        while (iterator.hasNext()) {
-            DataNode each = iterator.next();
-            if (schemaNames.contains(each.getSchemaName().toLowerCase())) {
-                iterator.remove();
-                configuration.getTables().remove(SingleTableLoadUtils.getDataNodeString(protocolType, each.getDataSourceName(), each.getSchemaName(), tableName));
+        for (String each : findMatchedTableKeys(tableName)) {
+            Collection<DataNode> dataNodes = singleTableDataNodes.get(each);
+            if (null == dataNodes) {
+                continue;
+            }
+            Iterator<DataNode> iterator = dataNodes.iterator();
+            while (iterator.hasNext()) {
+                DataNode dataNode = iterator.next();
+                if (schemaNames.contains(dataNode.getSchemaName().toLowerCase())) {
+                    iterator.remove();
+                    configuration.getTables().remove(SingleTableLoadUtils.getDataNodeString(protocolType, dataNode.getDataSourceName(), dataNode.getSchemaName(), each));
+                }
+            }
+            if (dataNodes.isEmpty()) {
+                singleTableDataNodes.remove(each);
+                tableMapperRuleAttribute.getLogicTableNames().remove(each);
             }
-        }
-        if (dataNodes.isEmpty()) {
-            singleTableDataNodes.remove(tableName.toLowerCase());
-            tableMapperRuleAttribute.getLogicTableNames().remove(tableName);
         }
     }
     
     @SuppressWarnings("CollectionWithoutInitialCapacity")
     @Override
     public Optional<DataNode> findTableDataNode(final String schemaName, final String tableName) {
-        Collection<DataNode> dataNodes = singleTableDataNodes.getOrDefault(tableName.toLowerCase(), new LinkedHashSet<>());
+        Collection<DataNode> dataNodes = findTableDataNodes(tableName);
         for (DataNode each : dataNodes) {
             if (schemaName.equalsIgnoreCase(each.getSchemaName())) {
                 return Optional.of(each);
@@ -111,6 +116,41 @@ public final class SingleMutableDataNodeRuleAttribute implements MutableDataNode
         return Optional.empty();
     }
     
+    /**
+     * Find table data nodes.
+     *
+     * @param tableName table name
+     * @return table data nodes
+     */
+    public Collection<DataNode> findTableDataNodes(final String tableName) {
+        for (String each : findMatchedTableKeys(tableName)) {
+            Collection<DataNode> dataNodes = singleTableDataNodes.get(each);
+            if (null != dataNodes) {
+                return dataNodes;
+            }
+        }
+        return Collections.emptyList();
+    }
+    
+    private Collection<String> findMatchedTableKeys(final String tableName) {
+        Collection<String> result = new LinkedList<>();
+        String formattedTableName = new DatabaseTypeRegistry(protocolType).formatIdentifierPattern(tableName);
+        if (singleTableDataNodes.containsKey(formattedTableName)) {
+            result.add(formattedTableName);
+            return result;
+        }
+        if (singleTableDataNodes.containsKey(tableName)) {
+            result.add(tableName);
+            return result;
+        }
+        for (Entry<String, Collection<DataNode>> entry : singleTableDataNodes.entrySet()) {
+            if (entry.getKey().equalsIgnoreCase(tableName)) {
+                result.add(entry.getKey());
+            }
+        }
+        return result;
+    }
+    
     @Override
     public ShardingSphereRule reloadRule(final RuleConfiguration ruleConfig, final String databaseName, final Map<String, DataSource> dataSourceMap,
                                          final Collection<ShardingSphereRule> builtRules) {
diff --git a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleTableMapperRuleAttribute.java b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleTableMapperRuleAttribute.java
index e338d4a410a..4e3de843b71 100644
--- a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleTableMapperRuleAttribute.java
+++ b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/rule/attribute/SingleTableMapperRuleAttribute.java
@@ -17,12 +17,12 @@
 
 package org.apache.shardingsphere.single.rule.attribute;
 
-import com.cedarsoftware.util.CaseInsensitiveSet;
 import org.apache.shardingsphere.infra.datanode.DataNode;
 import org.apache.shardingsphere.infra.rule.attribute.table.TableMapperRuleAttribute;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 
 /**
  * Single table mapper rule attribute.
@@ -36,7 +36,7 @@ public final class SingleTableMapperRuleAttribute implements TableMapperRuleAttr
     }
     
     private Collection<String> createLogicalTableNames(final Collection<Collection<DataNode>> singleTableDataNodes) {
-        Collection<String> result = new CaseInsensitiveSet<>(singleTableDataNodes.size(), 1F);
+        Collection<String> result = new LinkedHashSet<>(singleTableDataNodes.size(), 1F);
         singleTableDataNodes.forEach(each -> result.add(each.iterator().next().getTableName()));
         return result;
     }
diff --git a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/util/SingleTableLoadUtils.java b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/util/SingleTableLoadUtils.java
index c790fae9458..c9782810ec7 100644
--- a/kernel/single/core/src/main/java/org/apache/shardingsphere/single/util/SingleTableLoadUtils.java
+++ b/kernel/single/core/src/main/java/org/apache/shardingsphere/single/util/SingleTableLoadUtils.java
@@ -30,6 +30,7 @@ import org.apache.shardingsphere.infra.rule.attribute.table.TableMapperRuleAttri
 import org.apache.shardingsphere.single.constant.SingleTableConstants;
 
 import java.util.Collection;
+import java.util.LinkedList;
 import java.util.LinkedHashSet;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -107,7 +108,7 @@ public final class SingleTableLoadUtils {
      * @return data nodes
      */
     public static Collection<DataNode> convertToDataNodes(final String databaseName, final DatabaseType databaseType, final Collection<String> dataNodes) {
-        return dataNodes.stream().map(each -> new DataNode(databaseName, databaseType, each)).collect(Collectors.toCollection(() -> new LinkedHashSet<>(dataNodes.size(), 1F)));
+        return dataNodes.stream().map(each -> new DataNode(databaseName, databaseType, each)).collect(Collectors.toCollection(LinkedList::new));
     }
     
     /**
diff --git a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/datanode/SingleTableDataNodeLoaderTest.java b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/datanode/SingleTableDataNodeLoaderTest.java
index 8e8b04f07b7..02ec07589d0 100644
--- a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/datanode/SingleTableDataNodeLoaderTest.java
+++ b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/datanode/SingleTableDataNodeLoaderTest.java
@@ -146,6 +146,15 @@ class SingleTableDataNodeLoaderTest {
         assertThat(new TreeSet<>(actual.get("bar_tbl1").stream().map(DataNode::getDataSourceName).collect(Collectors.toList())), is(new TreeSet<>(Collections.singleton("bar_ds"))));
     }
     
+    @Test
+    void assertLoadWithDataSourceMapPreservesCaseSensitiveTableNames() throws SQLException {
+        Map<String, DataSource> localDataSourceMap = new LinkedHashMap<>(1, 1F);
+        localDataSourceMap.put("foo_ds", mockDataSource("foo_ds", Arrays.asList("Test3", "test3")));
+        Map<String, Collection<DataNode>> actual = SingleTableDataNodeLoader.load("foo_db", localDataSourceMap, Collections.emptyList());
+        assertTrue(actual.containsKey("Test3"));
+        assertTrue(actual.containsKey("test3"));
+    }
+    
     @Test
     void assertLoadSchemaTableNames() {
         Map<String, Collection<String>> actual = SingleTableDataNodeLoader.loadSchemaTableNames("foo_db", databaseType, dataSourceMap.get("foo_ds"), "foo_ds", Collections.singleton("foo_tbl2"));
diff --git a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/route/engine/SingleRouteEngineTest.java b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/route/engine/SingleRouteEngineTest.java
index 78a8e44943f..850e709a745 100644
--- a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/route/engine/SingleRouteEngineTest.java
+++ b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/route/engine/SingleRouteEngineTest.java
@@ -75,7 +75,7 @@ class SingleRouteEngineTest {
     @Test
     void assertRouteInSameDataSource() throws SQLException {
         SingleRouteEngine engine = new SingleRouteEngine(Arrays.asList(new QualifiedTable("foo_db", "t_order"), new QualifiedTable("foo_db", "t_order_item")), null, mock());
-        SingleRule singleRule = new SingleRule(new SingleRuleConfiguration(), "foo_db", mock(), createDataSourceMap(), Collections.emptyList());
+        SingleRule singleRule = new SingleRule(new SingleRuleConfiguration(), "foo_db", databaseType, createDataSourceMap(), Collections.emptyList());
         singleRule.getAttributes().getAttribute(DataNodeRuleAttribute.class).getAllDataNodes().put("t_order", Collections.singleton(new DataNode("ds_0", "foo_db", "t_order")));
         singleRule.getAttributes().getAttribute(DataNodeRuleAttribute.class).getAllDataNodes().put("t_order_item", Collections.singleton(new DataNode("ds_0", "foo_db", "t_order_item")));
         RouteContext routeContext = new RouteContext();
@@ -97,7 +97,7 @@ class SingleRouteEngineTest {
     void assertRouteWithoutSingleRule() throws SQLException {
         CreateTableStatement sqlStatement = CreateTableStatement.builder().databaseType(databaseType).build();
         SingleRouteEngine engine = new SingleRouteEngine(Arrays.asList(new QualifiedTable("foo_db", "t_order"), new QualifiedTable("foo_db", "t_order_item")), sqlStatement, mock());
-        SingleRule singleRule = new SingleRule(new SingleRuleConfiguration(), "foo_db", mock(), createDataSourceMap(), Collections.emptyList());
+        SingleRule singleRule = new SingleRule(new SingleRuleConfiguration(), "foo_db", databaseType, createDataSourceMap(), Collections.emptyList());
         RouteContext routeContext = new RouteContext();
         assertThat(routeContext.getOriginalDataNodes().size(), is(0));
         engine.route(routeContext, singleRule);
@@ -114,7 +114,7 @@ class SingleRouteEngineTest {
     void assertRouteWithDefaultSingleRule() throws SQLException {
         CreateTableStatement sqlStatement = CreateTableStatement.builder().databaseType(databaseType).build();
         SingleRouteEngine engine = new SingleRouteEngine(Arrays.asList(new QualifiedTable("foo_db", "t_order"), new QualifiedTable("foo_db", "t_order_item")), sqlStatement, mock());
-        SingleRule singleRule = new SingleRule(new SingleRuleConfiguration(Collections.emptyList(), "ds_0"), "foo_db", mock(), createDataSourceMap(), Collections.emptyList());
+        SingleRule singleRule = new SingleRule(new SingleRuleConfiguration(Collections.emptyList(), "ds_0"), "foo_db", databaseType, createDataSourceMap(), Collections.emptyList());
         RouteContext routeContext = new RouteContext();
         assertThat(routeContext.getRouteUnits().size(), is(0));
         engine.route(routeContext, singleRule);
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 ced44f028a7..fde01f3b9d5 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
@@ -134,6 +134,19 @@ class SingleMutableDataNodeRuleAttributeTest {
         }
     }
     
+    @Test
+    void assertFindTableDataNodesWithCaseSensitiveTables() {
+        SingleRuleConfiguration ruleConfig = new SingleRuleConfiguration(new LinkedList<>(Arrays.asList("foo_ds.public.Test3", "foo_ds.public.test3")), null);
+        Map<String, Collection<DataNode>> singleTableDataNodes = new LinkedHashMap<>(2, 1F);
+        singleTableDataNodes.put("Test3", new LinkedHashSet<>(Collections.singleton(new DataNode("foo_ds", "public", "Test3"))));
+        singleTableDataNodes.put("test3", new LinkedHashSet<>(Collections.singleton(new DataNode("foo_ds", "public", "test3"))));
+        SingleTableMapperRuleAttribute tableMapperRuleAttribute = new SingleTableMapperRuleAttribute(singleTableDataNodes.values());
+        SingleMutableDataNodeRuleAttribute ruleAttribute = createRuleAttribute(ruleConfig, Collections.singleton("foo_ds"), singleTableDataNodes, tableMapperRuleAttribute);
+        Collection<DataNode> actual = ruleAttribute.findTableDataNodes("Test3");
+        assertThat(actual.size(), is(1));
+        assertThat(actual.iterator().next().getTableName(), is("Test3"));
+    }
+    
     @Test
     void assertReloadRule() {
         SingleRuleConfiguration ruleConfig = new SingleRuleConfiguration(new LinkedList<>(Collections.emptyList()), null);
diff --git a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/attribute/SingleTableMapperRuleAttributeTest.java b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/attribute/SingleTableMapperRuleAttributeTest.java
index 5dab754d9cf..b473b9e24fa 100644
--- a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/attribute/SingleTableMapperRuleAttributeTest.java
+++ b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/rule/attribute/SingleTableMapperRuleAttributeTest.java
@@ -20,6 +20,7 @@ package org.apache.shardingsphere.single.rule.attribute;
 import org.apache.shardingsphere.infra.datanode.DataNode;
 import org.junit.jupiter.api.Test;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 
@@ -35,6 +36,14 @@ class SingleTableMapperRuleAttributeTest {
         assertThat(new LinkedList<>(ruleAttribute.getLogicTableNames()), is(Collections.singletonList("foo_tbl")));
     }
     
+    @Test
+    void assertGetLogicTableMapperWithCaseSensitiveTables() {
+        SingleTableMapperRuleAttribute actual = new SingleTableMapperRuleAttribute(Arrays.asList(
+                Collections.singleton(new DataNode("foo_ds", "public", "Test3")),
+                Collections.singleton(new DataNode("foo_ds", "public", "test3"))));
+        assertThat(new LinkedList<>(actual.getLogicTableNames()), is(Arrays.asList("Test3", "test3")));
+    }
+    
     @Test
     void assertGetDistributedTableMapper() {
         assertThat(new LinkedList<>(ruleAttribute.getDistributedTableNames()), is(Collections.emptyList()));
diff --git a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/util/SingleTableLoadUtilsTest.java b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/util/SingleTableLoadUtilsTest.java
index 2d2d3eaa794..f646959551b 100644
--- a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/util/SingleTableLoadUtilsTest.java
+++ b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/util/SingleTableLoadUtilsTest.java
@@ -36,6 +36,7 @@ import org.mockito.MockedConstruction;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.LinkedHashSet;
 import java.util.Optional;
 import java.util.TreeSet;
@@ -89,7 +90,7 @@ class SingleTableLoadUtilsTest {
         DataNode expectedDataNode1 = new DataNode("foo_ds", "foo_db", "foo_tbl");
         DataNode expectedDataNode2 = new DataNode("bar_ds", "foo_db", "bar_tbl");
         assertThat(SingleTableLoadUtils.convertToDataNodes("foo_db", databaseType, Arrays.asList("foo_ds.foo_tbl", "bar_ds.bar_tbl")),
-                is(new LinkedHashSet<>(Arrays.asList(expectedDataNode1, expectedDataNode2))));
+                is(new LinkedList<>(Arrays.asList(expectedDataNode1, expectedDataNode2))));
     }
     
     @Test