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));
}
}