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 443d736dd59 Reuse database connector metadata in MCP dialect (#39041)
443d736dd59 is described below

commit 443d736dd5945cf7c0474034569fcc9a75b5e965
Author: Liang Zhang <[email protected]>
AuthorDate: Wed Jul 8 00:50:25 2026 +0800

    Reuse database connector metadata in MCP dialect (#39041)
    
    Reuse database connector SPI metadata for MCP identifier quote, lower-case
    identifier handling, and equivalent system schema lookup while preserving 
MCP
    capability options as connector-absent and non-equivalent fallback behavior.
    Cover precedence and compatibility paths with scoped SPI mocks.
---
 .../database/capability/MCPDatabaseDialect.java    |  80 +++++++++++--
 .../capability/MCPDatabaseDialectTest.java         | 131 +++++++++++++++++++++
 2 files changed, 201 insertions(+), 10 deletions(-)

diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java
index 34d28c641a3..3357f634b33 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java
@@ -18,9 +18,15 @@
 package org.apache.shardingsphere.mcp.support.database.capability;
 
 import 
org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
+import 
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import 
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.IdentifierPatternType;
+import 
org.apache.shardingsphere.database.connector.core.metadata.database.system.SystemDatabase;
 import 
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicy;
 import 
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
+import 
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicySet;
 import 
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
+import 
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 
 import java.util.Collection;
@@ -34,10 +40,17 @@ public final class MCPDatabaseDialect {
     
     private final String databaseType;
     
+    private final Optional<DialectDatabaseMetaData> dialectDatabaseMetaData;
+    
+    private final Optional<SystemDatabase> systemDatabase;
+    
     private final Optional<MCPDatabaseCapabilityOption> option;
     
-    private MCPDatabaseDialect(final String databaseType, final 
Optional<MCPDatabaseCapabilityOption> option) {
+    private MCPDatabaseDialect(final String databaseType, final 
Optional<DialectDatabaseMetaData> dialectDatabaseMetaData, final 
Optional<SystemDatabase> systemDatabase,
+                               final Optional<MCPDatabaseCapabilityOption> 
option) {
         this.databaseType = databaseType;
+        this.dialectDatabaseMetaData = dialectDatabaseMetaData;
+        this.systemDatabase = systemDatabase;
         this.option = option;
     }
     
@@ -49,10 +62,17 @@ public final class MCPDatabaseDialect {
      */
     public static MCPDatabaseDialect of(final String databaseType) {
         String actualDatabaseType = trimToEmpty(databaseType);
+        Optional<DatabaseType> databaseTypeFromSPI = 
actualDatabaseType.isEmpty()
+                ? Optional.empty()
+                : TypedSPILoader.findService(DatabaseType.class, 
actualDatabaseType);
         Optional<MCPDatabaseCapabilityOption> option = 
actualDatabaseType.isEmpty()
                 ? Optional.empty()
                 : 
TypedSPILoader.findService(MCPDatabaseCapabilityOption.class, 
actualDatabaseType);
-        return new MCPDatabaseDialect(actualDatabaseType, option);
+        return new MCPDatabaseDialect(actualDatabaseType, 
databaseTypeFromSPI.flatMap(MCPDatabaseDialect::findDialectDatabaseMetaData), 
databaseTypeFromSPI.map(SystemDatabase::new), option);
+    }
+    
+    private static Optional<DialectDatabaseMetaData> 
findDialectDatabaseMetaData(final DatabaseType databaseType) {
+        return 
DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseType);
     }
     
     /**
@@ -61,7 +81,12 @@ public final class MCPDatabaseDialect {
      * @return identifier quote character
      */
     public QuoteCharacter getIdentifierQuoteCharacter() {
-        return 
option.map(MCPDatabaseCapabilityOption::getIdentifierQuoteCharacter).orElseGet(this::getFallbackIdentifierQuoteCharacter);
+        return 
dialectDatabaseMetaData.map(DialectDatabaseMetaData::getQuoteCharacter)
+                .orElseGet(() -> 
option.map(MCPDatabaseCapabilityOption::getIdentifierQuoteCharacter).orElseGet(this::getFallbackIdentifierQuoteCharacter));
+    }
+    
+    private QuoteCharacter getFallbackIdentifierQuoteCharacter() {
+        return databaseType.isEmpty() ? QuoteCharacter.BACK_QUOTE : 
QuoteCharacter.QUOTE;
     }
     
     /**
@@ -71,8 +96,23 @@ public final class MCPDatabaseDialect {
      * @return identifier case policy
      */
     public IdentifierCasePolicy getIdentifierCasePolicy(final IdentifierScope 
identifierScope) {
-        return 
option.map(MCPDatabaseCapabilityOption::getIdentifierCasePolicySet)
-                
.orElseGet(IdentifierCasePolicyFactory::newSensitivePolicySet).getPolicy(identifierScope);
+        return getIdentifierCasePolicySet().getPolicy(identifierScope);
+    }
+    
+    private IdentifierCasePolicySet getIdentifierCasePolicySet() {
+        return findLowerCaseDialectIdentifierCasePolicySet()
+                .orElseGet(() -> 
option.map(MCPDatabaseCapabilityOption::getIdentifierCasePolicySet).orElseGet(IdentifierCasePolicyFactory::newSensitivePolicySet));
+    }
+    
+    private Optional<IdentifierCasePolicySet> 
findLowerCaseDialectIdentifierCasePolicySet() {
+        return 
dialectDatabaseMetaData.flatMap(MCPDatabaseDialect::createLowerCaseDialectIdentifierCasePolicySet);
+    }
+    
+    private static Optional<IdentifierCasePolicySet> 
createLowerCaseDialectIdentifierCasePolicySet(final DialectDatabaseMetaData 
dialectDatabaseMetaData) {
+        IdentifierPatternType identifierPatternType = 
dialectDatabaseMetaData.getIdentifierPatternType();
+        return IdentifierPatternType.LOWER_CASE == identifierPatternType
+                ? 
Optional.of(IdentifierCasePolicyFactory.newDialectDefaultPolicySet(identifierPatternType,
 dialectDatabaseMetaData.isCaseSensitive()))
+                : Optional.empty();
     }
     
     /**
@@ -90,7 +130,11 @@ public final class MCPDatabaseDialect {
      * @return whether unquoted identifiers are folded
      */
     public boolean isUnquotedIdentifierCaseFolded() {
-        return 
option.map(MCPDatabaseCapabilityOption::isUnquotedIdentifierCaseFolded).orElse(false);
+        return isLowerCaseDialectIdentifierPattern() || 
option.map(MCPDatabaseCapabilityOption::isUnquotedIdentifierCaseFolded).orElse(false);
+    }
+    
+    private boolean isLowerCaseDialectIdentifierPattern() {
+        return dialectDatabaseMetaData.map(each -> 
IdentifierPatternType.LOWER_CASE == 
each.getIdentifierPatternType()).orElse(false);
     }
     
     /**
@@ -122,8 +166,7 @@ public final class MCPDatabaseDialect {
         if (actualSchemaName.isEmpty()) {
             return false;
         }
-        Collection<String> systemSchemas = 
option.map(MCPDatabaseCapabilityOption::getSystemSchemas).orElseGet(List::of);
-        return containsSystemSchema(systemSchemas, actualSchemaName);
+        return containsSystemSchema(getSystemSchemas(), actualSchemaName);
     }
     
     /**
@@ -138,8 +181,25 @@ public final class MCPDatabaseDialect {
         return isSystemSchema(schemaName) || 
SchemaSemantics.DATABASE_AS_SCHEMA == defaultSchemaSemantics && 
isSystemSchema(catalogName);
     }
     
-    private QuoteCharacter getFallbackIdentifierQuoteCharacter() {
-        return databaseType.isEmpty() ? QuoteCharacter.BACK_QUOTE : 
QuoteCharacter.QUOTE;
+    private Collection<String> getSystemSchemas() {
+        Collection<String> optionSystemSchemas = 
option.map(MCPDatabaseCapabilityOption::getSystemSchemas).orElseGet(List::of);
+        return findDialectSystemSchemas().filter(each -> 
optionSystemSchemas.isEmpty() || containsSameSystemSchemas(each, 
optionSystemSchemas)).orElse(optionSystemSchemas);
+    }
+    
+    private Optional<Collection<String>> findDialectSystemSchemas() {
+        return 
systemDatabase.map(SystemDatabase::getSystemSchemas).filter(each -> 
!each.isEmpty());
+    }
+    
+    private static boolean containsSameSystemSchemas(final Collection<String> 
systemSchemas, final Collection<String> expectedSystemSchemas) {
+        if (systemSchemas.size() != expectedSystemSchemas.size()) {
+            return false;
+        }
+        for (String each : systemSchemas) {
+            if (!containsSystemSchema(expectedSystemSchemas, each)) {
+                return false;
+            }
+        }
+        return true;
     }
     
     private static boolean containsSystemSchema(final Collection<String> 
systemSchemas, final String schemaName) {
diff --git 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java
index 4fa2e97136c..165d56c04cf 100644
--- 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java
@@ -18,19 +18,32 @@
 package org.apache.shardingsphere.mcp.support.database.capability;
 
 import 
org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
+import 
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import 
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.IdentifierPatternType;
+import 
org.apache.shardingsphere.database.connector.core.metadata.database.system.DialectSystemDatabase;
+import 
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
 import 
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicy;
 import 
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
+import 
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 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 org.mockito.MockedStatic;
 
+import java.util.List;
+import java.util.Optional;
 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.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
 
 class MCPDatabaseDialectTest {
     
@@ -47,12 +60,42 @@ class MCPDatabaseDialectTest {
         assertThat(actual, is(expected));
     }
     
+    @Test
+    void assertGetIdentifierQuoteCharacterFromDialectDatabaseMetaData() {
+        try (
+                MockedStatic<TypedSPILoader> typedSPILoader = 
mockStatic(TypedSPILoader.class);
+                MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = 
mockStatic(DatabaseTypedSPILoader.class)) {
+            DatabaseType databaseType = mockDatabaseType("FixtureWithOption", 
typedSPILoader);
+            DialectDatabaseMetaData dialectDatabaseMetaData = 
mockDialectDatabaseMetaData(databaseType, databaseTypedSPILoader);
+            
when(dialectDatabaseMetaData.getQuoteCharacter()).thenReturn(QuoteCharacter.BRACKETS);
+            MCPDatabaseCapabilityOption option = 
mockMCPDatabaseCapabilityOption("FixtureWithOption", typedSPILoader);
+            
when(option.getIdentifierQuoteCharacter()).thenReturn(QuoteCharacter.BACK_QUOTE);
+            QuoteCharacter actual = 
MCPDatabaseDialect.of("FixtureWithOption").getIdentifierQuoteCharacter();
+            assertThat(actual, is(QuoteCharacter.BRACKETS));
+        }
+    }
+    
     @Test
     void assertGetIdentifierCasePolicyWithCaseInsensitiveIdentifier() {
         IdentifierCasePolicy actual = 
MCPDatabaseDialect.of("MySQL").getIdentifierCasePolicy(IdentifierScope.TABLE);
         assertTrue(actual.matches("phone", "Phone", QuoteCharacter.QUOTE));
     }
     
+    @Test
+    void assertGetIdentifierCasePolicyFromLowerCaseDialectDatabaseMetaData() {
+        try (
+                MockedStatic<TypedSPILoader> typedSPILoader = 
mockStatic(TypedSPILoader.class);
+                MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = 
mockStatic(DatabaseTypedSPILoader.class)) {
+            DatabaseType databaseType = mockDatabaseType("FixtureWithOption", 
typedSPILoader);
+            DialectDatabaseMetaData dialectDatabaseMetaData = 
mockDialectDatabaseMetaData(databaseType, databaseTypedSPILoader);
+            
when(dialectDatabaseMetaData.getIdentifierPatternType()).thenReturn(IdentifierPatternType.LOWER_CASE);
+            MCPDatabaseCapabilityOption option = 
mockMCPDatabaseCapabilityOption("FixtureWithOption", typedSPILoader);
+            
when(option.getIdentifierCasePolicySet()).thenReturn(IdentifierCasePolicyFactory.newSensitivePolicySet());
+            IdentifierCasePolicy actual = 
MCPDatabaseDialect.of("FixtureWithOption").getIdentifierCasePolicy(IdentifierScope.TABLE);
+            assertTrue(actual.matches("fixture", "Fixture", 
QuoteCharacter.NONE));
+        }
+    }
+    
     @ParameterizedTest(name = "{0}")
     @MethodSource("getDefaultSchemaSemanticsArguments")
     void assertGetDefaultSchemaSemantics(final String name, final String 
databaseType, final SchemaSemantics expected) {
@@ -67,6 +110,19 @@ class MCPDatabaseDialectTest {
         assertThat(actual, is(expected));
     }
     
+    @Test
+    void assertIsUnquotedIdentifierCaseFoldedFromDialectDatabaseMetaData() {
+        try (
+                MockedStatic<TypedSPILoader> typedSPILoader = 
mockStatic(TypedSPILoader.class);
+                MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = 
mockStatic(DatabaseTypedSPILoader.class)) {
+            DatabaseType databaseType = mockDatabaseType("Fixture", 
typedSPILoader);
+            DialectDatabaseMetaData dialectDatabaseMetaData = 
mockDialectDatabaseMetaData(databaseType, databaseTypedSPILoader);
+            
when(dialectDatabaseMetaData.getIdentifierPatternType()).thenReturn(IdentifierPatternType.LOWER_CASE);
+            boolean actual = 
MCPDatabaseDialect.of("Fixture").isUnquotedIdentifierCaseFolded();
+            assertTrue(actual);
+        }
+    }
+    
     @ParameterizedTest(name = "{0}")
     @MethodSource("getSequenceQueryArguments")
     void assertGetSequenceQuery(final String name, final String databaseType, 
final String expected) {
@@ -93,6 +149,52 @@ class MCPDatabaseDialectTest {
         assertTrue(actual);
     }
     
+    @Test
+    void assertIsSystemSchemaFromDialectSystemDatabase() {
+        try (
+                MockedStatic<TypedSPILoader> typedSPILoader = 
mockStatic(TypedSPILoader.class);
+                MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = 
mockStatic(DatabaseTypedSPILoader.class)) {
+            DatabaseType databaseType = mockDatabaseType("Fixture", 
typedSPILoader);
+            mockDialectDatabaseMetaDataAbsent(databaseType, 
databaseTypedSPILoader);
+            DialectSystemDatabase dialectSystemDatabase = 
mockDialectSystemDatabase(databaseType, databaseTypedSPILoader);
+            
when(dialectSystemDatabase.getSystemSchemas()).thenReturn(List.of("fixture_system"));
+            boolean actual = 
MCPDatabaseDialect.of("Fixture").isSystemSchema("fixture_system");
+            assertTrue(actual);
+        }
+    }
+    
+    @Test
+    void assertIsSystemSchemaWithOptionSystemSchema() {
+        try (
+                MockedStatic<TypedSPILoader> typedSPILoader = 
mockStatic(TypedSPILoader.class);
+                MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = 
mockStatic(DatabaseTypedSPILoader.class)) {
+            DatabaseType databaseType = mockDatabaseType("FixtureWithOption", 
typedSPILoader);
+            mockDialectDatabaseMetaDataAbsent(databaseType, 
databaseTypedSPILoader);
+            DialectSystemDatabase dialectSystemDatabase = 
mockDialectSystemDatabase(databaseType, databaseTypedSPILoader);
+            
when(dialectSystemDatabase.getSystemSchemas()).thenReturn(List.of("dialect_system"));
+            MCPDatabaseCapabilityOption option = 
mockMCPDatabaseCapabilityOption("FixtureWithOption", typedSPILoader);
+            
when(option.getSystemSchemas()).thenReturn(List.of("option_system"));
+            boolean actual = 
MCPDatabaseDialect.of("FixtureWithOption").isSystemSchema("option_system");
+            assertTrue(actual);
+        }
+    }
+    
+    @Test
+    void assertIsSystemSchemaIgnoresDifferentDialectSystemSchema() {
+        try (
+                MockedStatic<TypedSPILoader> typedSPILoader = 
mockStatic(TypedSPILoader.class);
+                MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = 
mockStatic(DatabaseTypedSPILoader.class)) {
+            DatabaseType databaseType = mockDatabaseType("FixtureWithOption", 
typedSPILoader);
+            mockDialectDatabaseMetaDataAbsent(databaseType, 
databaseTypedSPILoader);
+            DialectSystemDatabase dialectSystemDatabase = 
mockDialectSystemDatabase(databaseType, databaseTypedSPILoader);
+            
when(dialectSystemDatabase.getSystemSchemas()).thenReturn(List.of("dialect_system"));
+            MCPDatabaseCapabilityOption option = 
mockMCPDatabaseCapabilityOption("FixtureWithOption", typedSPILoader);
+            
when(option.getSystemSchemas()).thenReturn(List.of("option_system"));
+            boolean actual = 
MCPDatabaseDialect.of("FixtureWithOption").isSystemSchema("dialect_system");
+            assertFalse(actual);
+        }
+    }
+    
     @Test
     void assertIsSystemSchemaWithUserSchema() {
         boolean actual = 
MCPDatabaseDialect.of("MySQL").isSystemSchema("orders");
@@ -128,6 +230,35 @@ class MCPDatabaseDialectTest {
                 Arguments.of("unknown", "FixtureDB", false));
     }
     
+    private static DatabaseType mockDatabaseType(final String databaseType, 
final MockedStatic<TypedSPILoader> typedSPILoader) {
+        DatabaseType result = mock(DatabaseType.class);
+        typedSPILoader.when(() -> 
TypedSPILoader.findService(DatabaseType.class, 
databaseType)).thenReturn(Optional.of(result));
+        typedSPILoader.when(() -> 
TypedSPILoader.findService(MCPDatabaseCapabilityOption.class, 
databaseType)).thenReturn(Optional.empty());
+        return result;
+    }
+    
+    private static DialectDatabaseMetaData mockDialectDatabaseMetaData(final 
DatabaseType databaseType, final MockedStatic<DatabaseTypedSPILoader> 
databaseTypedSPILoader) {
+        DialectDatabaseMetaData result = mock(DialectDatabaseMetaData.class);
+        databaseTypedSPILoader.when(() -> 
DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, 
databaseType)).thenReturn(Optional.of(result));
+        return result;
+    }
+    
+    private static void mockDialectDatabaseMetaDataAbsent(final DatabaseType 
databaseType, final MockedStatic<DatabaseTypedSPILoader> 
databaseTypedSPILoader) {
+        databaseTypedSPILoader.when(() -> 
DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, 
databaseType)).thenReturn(Optional.empty());
+    }
+    
+    private static MCPDatabaseCapabilityOption 
mockMCPDatabaseCapabilityOption(final String databaseType, final 
MockedStatic<TypedSPILoader> typedSPILoader) {
+        MCPDatabaseCapabilityOption result = 
mock(MCPDatabaseCapabilityOption.class);
+        typedSPILoader.when(() -> 
TypedSPILoader.findService(MCPDatabaseCapabilityOption.class, 
databaseType)).thenReturn(Optional.of(result));
+        return result;
+    }
+    
+    private static DialectSystemDatabase mockDialectSystemDatabase(final 
DatabaseType databaseType, final MockedStatic<DatabaseTypedSPILoader> 
databaseTypedSPILoader) {
+        DialectSystemDatabase result = mock(DialectSystemDatabase.class);
+        databaseTypedSPILoader.when(() -> 
DatabaseTypedSPILoader.findService(DialectSystemDatabase.class, 
databaseType)).thenReturn(Optional.of(result));
+        return result;
+    }
+    
     private static Stream<Arguments> getDefaultSchemaSemanticsArguments() {
         return Stream.of(
                 Arguments.of("mysql", "MySQL", 
SchemaSemantics.DATABASE_AS_SCHEMA),

Reply via email to