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 b71fbdfc4eb Fix MySQLMetaDataLoader NPE risk when JDBC catalog is null 
(#38855)
b71fbdfc4eb is described below

commit b71fbdfc4ebe57977d5e4e5ecc6dab5ba1e4e24d
Author: Nassir Sultan <[email protected]>
AuthorDate: Sun Jun 21 08:21:27 2026 +0530

    Fix MySQLMetaDataLoader NPE risk when JDBC catalog is null (#38855)
    
    * Fix MySQLMetaDataLoader NPE risk when JDBC catalog is null
    
    Resolves issue where ShardingSphere-JDBC fails with
    TableNotExistsException
    when connecting to MySQL via a JDBC URL with no schema specified.
    getDatabaseName() checked catalog with "".equals(...), which doesn't
    catch a null catalog (returned by MySQL's getCatalog() when no schema
    is set), causing the cached-database-name fallback to be skipped.
    
    Replaced the check with Guava's Strings.isNullOrEmpty(), consistent
    with existing usage elsewhere in the same file.
    
    Added a regression test (null catalog fallback case) to
    MySQLMetaDataLoaderTest, verified to fail without this fix.
    
    Partially addresses #28469
    
    * Guard against empty tableNames in getDatabaseName fallback
    
    Addresses review feedback on #38855: the null-catalog fallback assumed
    a table name was always available to look up in the cache, but
    loadColumnMetaDataMap can be called with an empty actualTableNames
    list (the "load every table" case, which happens at startup before
    any specific table has been requested). That made
    tableNames.iterator().next() throw NoSuchElementException instead of
    the TableNotExistsException the original fix was meant to prevent.
    
    getDatabaseName() now checks tableNames.isEmpty() before attempting
    the cache lookup, returning null gracefully in that case rather than
    crashing. A null database name simply means the metadata query finds
    no matching tables, which the loader already handles correctly
    elsewhere.
    
    Added assertLoadWithNullCatalogAndNoRequestedTableNames, verified to
    fail with NoSuchElementException without this fix.
---
 .../metadata/data/loader/MySQLMetaDataLoader.java  |  6 +++++-
 .../data/loader/MySQLMetaDataLoaderTest.java       | 24 +++++++++++++++++-----
 2 files changed, 24 insertions(+), 6 deletions(-)

diff --git 
a/database/connector/dialect/mysql/src/main/java/org/apache/shardingsphere/database/connector/mysql/metadata/data/loader/MySQLMetaDataLoader.java
 
b/database/connector/dialect/mysql/src/main/java/org/apache/shardingsphere/database/connector/mysql/metadata/data/loader/MySQLMetaDataLoader.java
index 1395ec9c5be..a682daabe9c 100644
--- 
a/database/connector/dialect/mysql/src/main/java/org/apache/shardingsphere/database/connector/mysql/metadata/data/loader/MySQLMetaDataLoader.java
+++ 
b/database/connector/dialect/mysql/src/main/java/org/apache/shardingsphere/database/connector/mysql/metadata/data/loader/MySQLMetaDataLoader.java
@@ -206,7 +206,11 @@ public final class MySQLMetaDataLoader implements 
DialectMetaDataLoader {
     }
     
     private String getDatabaseName(final Connection connection, final 
Collection<String> tableNames) throws SQLException {
-        return "".equals(connection.getCatalog()) ? 
GlobalDataSourceRegistry.getInstance().getCachedDatabaseTables().get(tableNames.iterator().next())
 : connection.getCatalog();
+        String catalog = connection.getCatalog();
+        if (!Strings.isNullOrEmpty(catalog)) {
+            return catalog;
+        }
+        return tableNames.isEmpty() ? null : 
GlobalDataSourceRegistry.getInstance().getCachedDatabaseTables().get(tableNames.iterator().next());
     }
     
     @Override
diff --git 
a/database/connector/dialect/mysql/src/test/java/org/apache/shardingsphere/database/connector/mysql/metadata/data/loader/MySQLMetaDataLoaderTest.java
 
b/database/connector/dialect/mysql/src/test/java/org/apache/shardingsphere/database/connector/mysql/metadata/data/loader/MySQLMetaDataLoaderTest.java
index a1e9d6f54b1..c4b949b0c10 100644
--- 
a/database/connector/dialect/mysql/src/test/java/org/apache/shardingsphere/database/connector/mysql/metadata/data/loader/MySQLMetaDataLoaderTest.java
+++ 
b/database/connector/dialect/mysql/src/test/java/org/apache/shardingsphere/database/connector/mysql/metadata/data/loader/MySQLMetaDataLoaderTest.java
@@ -50,6 +50,7 @@ import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 class MySQLMetaDataLoaderTest {
@@ -83,11 +84,22 @@ class MySQLMetaDataLoaderTest {
         assertTrue(actual.iterator().next().getTables().isEmpty());
     }
     
+    @SuppressWarnings("JDBCResourceOpenedButNotSafelyClosed")
+    @Test
+    void assertLoadWithNullCatalogAndNoRequestedTableNames() throws 
SQLException {
+        DataSource dataSource = mockDataSource();
+        when(dataSource.getConnection().getCatalog()).thenReturn(null);
+        DataTypeRegistry.load(dataSource, "MySQL");
+        Collection<SchemaMetaData> actual = dialectMetaDataLoader.load(new 
MetaDataLoaderMaterial(Collections.emptyList(), "foo_ds", dataSource, 
databaseType, "sharding_db"));
+        assertThat(actual.size(), is(1));
+        assertTrue(actual.iterator().next().getTables().isEmpty());
+    }
+    
     @SuppressWarnings({"JDBCResourceOpenedButNotSafelyClosed", "resource"})
     @ParameterizedTest(name = "{0}")
     @MethodSource("loadArguments")
     void assertLoad(final String name, final Collection<String> 
actualTableNames, final String tableMetaDataSQL,
-                    final boolean emptyCatalog, final boolean includeView, 
final boolean includeConstraints, final boolean includeCompositeIndex) throws 
SQLException {
+                    final String catalog, final String expectedDatabaseName, 
final boolean includeView, final boolean includeConstraints, final boolean 
includeCompositeIndex) throws SQLException {
         DataSource dataSource = mockDataSource();
         ResultSet tableMetaDataResultSet = mockTableMetaDataResultSet();
         
when(dataSource.getConnection().prepareStatement(tableMetaDataSQL).executeQuery()).thenReturn(tableMetaDataResultSet);
@@ -105,10 +117,11 @@ class MySQLMetaDataLoaderTest {
         Map<String, String> cachedDatabaseTables = 
GlobalDataSourceRegistry.getInstance().getCachedDatabaseTables();
         String previous = cachedDatabaseTables.put("tbl", "fallback_db");
         try {
-            
when(dataSource.getConnection().getCatalog()).thenReturn(emptyCatalog ? "" : 
"sharding_db");
+            when(dataSource.getConnection().getCatalog()).thenReturn(catalog);
             DataTypeRegistry.load(dataSource, "MySQL");
             Collection<SchemaMetaData> actual = dialectMetaDataLoader.load(new 
MetaDataLoaderMaterial(actualTableNames, "foo_ds", dataSource, databaseType, 
"sharding_db"));
             assertTableMetaData(actual, includeConstraints, 
includeCompositeIndex);
+            
verify(dataSource.getConnection().prepareStatement(tableMetaDataSQL)).setString(1,
 expectedDatabaseName);
         } finally {
             if (null == previous) {
                 cachedDatabaseTables.remove("tbl");
@@ -237,8 +250,9 @@ class MySQLMetaDataLoaderTest {
     
     private static Stream<Arguments> loadArguments() {
         return Stream.of(
-                Arguments.of("load without requested table names", 
Collections.emptyList(), TABLE_METADATA_SQL, false, false, false, false),
-                Arguments.of("load with requested table names", 
Collections.singletonList("tbl"), TABLE_METADATA_SQL_WITH_TABLE, false, false, 
false, false),
-                Arguments.of("load with empty catalog fallback and view 
constraints", Collections.singletonList("tbl"), TABLE_METADATA_SQL_WITH_TABLE, 
true, true, true, true));
+                Arguments.of("load without requested table names", 
Collections.emptyList(), TABLE_METADATA_SQL, "sharding_db", "sharding_db", 
false, false, false),
+                Arguments.of("load with requested table names", 
Collections.singletonList("tbl"), TABLE_METADATA_SQL_WITH_TABLE, "sharding_db", 
"sharding_db", false, false, false),
+                Arguments.of("load with empty catalog fallback and view 
constraints", Collections.singletonList("tbl"), TABLE_METADATA_SQL_WITH_TABLE, 
"", "fallback_db", true, true, true),
+                Arguments.of("load with null catalog fallback", 
Collections.singletonList("tbl"), TABLE_METADATA_SQL_WITH_TABLE, null, 
"fallback_db", true, true, true));
     }
 }

Reply via email to