This is an automated email from the ASF dual-hosted git repository.
menghaoranss 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 c8667e5f003 Add identifier matching to support schema case policies
(#39170)
c8667e5f003 is described below
commit c8667e5f003f13ef9e9775aad6346dbcc896e6ec
Author: Haoran Meng <[email protected]>
AuthorDate: Sat Jul 18 21:29:24 2026 +0800
Add identifier matching to support schema case policies (#39170)
---
.../identifier/IdentifierNormalizeEngine.java | 19 +++++++++++---
.../identifier/IdentifierNormalizeEngineTest.java | 22 +++++++++++++---
.../handler/update/LoadSingleTableExecutor.java | 25 ++++++++++++++----
.../update/LoadSingleTableExecutorTest.java | 30 ++++++++++++++--------
4 files changed, 73 insertions(+), 23 deletions(-)
diff --git
a/database/connector/core/src/main/java/org/apache/shardingsphere/database/connector/core/metadata/identifier/IdentifierNormalizeEngine.java
b/database/connector/core/src/main/java/org/apache/shardingsphere/database/connector/core/metadata/identifier/IdentifierNormalizeEngine.java
index c14c3ab2c78..89d7178e010 100644
---
a/database/connector/core/src/main/java/org/apache/shardingsphere/database/connector/core/metadata/identifier/IdentifierNormalizeEngine.java
+++
b/database/connector/core/src/main/java/org/apache/shardingsphere/database/connector/core/metadata/identifier/IdentifierNormalizeEngine.java
@@ -24,6 +24,8 @@ import
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoa
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import javax.sql.DataSource;
+import java.util.Collection;
+import java.util.Optional;
/**
* Identifier normalize engine.
@@ -54,9 +56,6 @@ public final class IdentifierNormalizeEngine {
* @return normalized identifier
*/
public static String normalize(final IdentifierCasePolicy policy, final
String identifier) {
- if (null == identifier) {
- return null;
- }
QuoteCharacter quoteCharacter =
QuoteCharacter.getQuoteCharacter(identifier);
String unwrappedIdentifier = quoteCharacter.unwrap(identifier);
if (QuoteCharacter.NONE != quoteCharacter) {
@@ -64,4 +63,18 @@ public final class IdentifierNormalizeEngine {
}
return LookupMode.NORMALIZED ==
policy.getLookupMode(QuoteCharacter.NONE) ?
policy.normalize(unwrappedIdentifier) : unwrappedIdentifier;
}
+
+ /**
+ * Find matched stored identifier.
+ *
+ * @param storedNames stored identifier names
+ * @param policy identifier case policy
+ * @param identifier identifier
+ * @return matched stored identifier
+ */
+ public static Optional<String> findMatchedIdentifier(final
Collection<String> storedNames, final IdentifierCasePolicy policy, final String
identifier) {
+ QuoteCharacter quoteCharacter =
QuoteCharacter.getQuoteCharacter(identifier);
+ String unwrappedIdentifier = quoteCharacter.unwrap(identifier);
+ return storedNames.stream().filter(each -> policy.matches(each,
unwrappedIdentifier, quoteCharacter)).findFirst();
+ }
}
diff --git
a/database/connector/core/src/test/java/org/apache/shardingsphere/database/connector/core/metadata/identifier/IdentifierNormalizeEngineTest.java
b/database/connector/core/src/test/java/org/apache/shardingsphere/database/connector/core/metadata/identifier/IdentifierNormalizeEngineTest.java
index a3f50b9a7ea..8b2bd6f5dbc 100644
---
a/database/connector/core/src/test/java/org/apache/shardingsphere/database/connector/core/metadata/identifier/IdentifierNormalizeEngineTest.java
+++
b/database/connector/core/src/test/java/org/apache/shardingsphere/database/connector/core/metadata/identifier/IdentifierNormalizeEngineTest.java
@@ -21,9 +21,11 @@ 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 java.util.Collections;
+import java.util.Optional;
+
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-import static org.junit.jupiter.api.Assertions.assertNull;
class IdentifierNormalizeEngineTest {
@@ -48,8 +50,20 @@ class IdentifierNormalizeEngineTest {
}
@Test
- void assertNormalizeNullIdentifier() {
- IdentifierCasePolicy policy =
IdentifierNormalizeEngine.resolvePolicy(databaseType, null,
IdentifierScope.TABLE);
- assertNull(IdentifierNormalizeEngine.normalize(policy, null));
+ void assertFindMatchedIdentifierWithUnquotedIdentifier() {
+ IdentifierCasePolicy policy =
IdentifierCasePolicyFactory.newInsensitivePolicySet().getPolicy(IdentifierScope.TABLE);
+
assertThat(IdentifierNormalizeEngine.findMatchedIdentifier(Collections.singletonList("Foo_Tbl"),
policy, "foo_tbl"), is(Optional.of("Foo_Tbl")));
+ }
+
+ @Test
+ void assertFindMatchedIdentifierWithQuotedIdentifier() {
+ IdentifierCasePolicy policy =
IdentifierCasePolicyFactory.newInsensitivePolicySet().getPolicy(IdentifierScope.TABLE);
+
assertThat(IdentifierNormalizeEngine.findMatchedIdentifier(Collections.singletonList("Foo_Tbl"),
policy, "\"Foo_Tbl\""), is(Optional.of("Foo_Tbl")));
+ }
+
+ @Test
+ void assertFindMatchedIdentifierWhenNotMatched() {
+ IdentifierCasePolicy policy =
IdentifierCasePolicyFactory.newSensitivePolicySet().getPolicy(IdentifierScope.TABLE);
+
assertThat(IdentifierNormalizeEngine.findMatchedIdentifier(Collections.singletonList("Foo_Tbl"),
policy, "foo_tbl"), is(Optional.empty()));
}
}
diff --git
a/kernel/single/distsql/handler/src/main/java/org/apache/shardingsphere/single/distsql/handler/update/LoadSingleTableExecutor.java
b/kernel/single/distsql/handler/src/main/java/org/apache/shardingsphere/single/distsql/handler/update/LoadSingleTableExecutor.java
index 0a627bdefc4..f9fc406687b 100644
---
a/kernel/single/distsql/handler/src/main/java/org/apache/shardingsphere/single/distsql/handler/update/LoadSingleTableExecutor.java
+++
b/kernel/single/distsql/handler/src/main/java/org/apache/shardingsphere/single/distsql/handler/update/LoadSingleTableExecutor.java
@@ -19,6 +19,9 @@ package
org.apache.shardingsphere.single.distsql.handler.update;
import lombok.Setter;
import
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicy;
+import
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierNormalizeEngine;
+import
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
import
org.apache.shardingsphere.database.exception.core.exception.syntax.table.TableExistsException;
@@ -110,6 +113,7 @@ public final class LoadSingleTableExecutor implements
DatabaseRuleCreateExecutor
String.join(",", invalidDataSources), String.join(",",
aggregatedDataSourceMap.keySet()))));
Map<String, DatabaseType> storageUnitDatabaseTypes =
getStorageUnitDatabaseTypes(storageUnitNames, aggregatedDataSourceMap);
Map<String, Map<String, Collection<String>>> actualTableNodes =
getActualTableNodes(storageUnitNames, aggregatedDataSourceMap,
storageUnitDatabaseTypes);
+ Map<String, IdentifierCasePolicy> schemaPolicies = new
LinkedHashMap<>(actualTableNodes.size());
for (SingleTableSegment each : sqlStatement.getTables()) {
String tableName = each.getTableName();
if (!SingleTableConstants.ASTERISK.equals(tableName)) {
@@ -117,15 +121,26 @@ public final class LoadSingleTableExecutor implements
DatabaseRuleCreateExecutor
ShardingSpherePreconditions.checkState(actualTableNodes.containsKey(storageUnitName),
() -> new TableNotFoundException(tableName,
storageUnitName));
DatabaseType storageUnitDatabaseType =
storageUnitDatabaseTypes.get(storageUnitName);
- String actualSchemaName =
isSameDatabaseType(database.getProtocolType(), storageUnitDatabaseType) ?
defaultSchemaName
- : new
DatabaseTypeRegistry(storageUnitDatabaseType).formatIdentifierPattern(defaultSchemaName);
- String schemaName = each.getSchemaName().isPresent() ?
each.getSchemaName().get() : actualSchemaName;
-
ShardingSpherePreconditions.checkState(actualTableNodes.get(storageUnitName).get(schemaName).contains(tableName),
- () -> new TableNotFoundException(tableName,
storageUnitName));
+ Map<String, Collection<String>> schemaTableNames =
actualTableNodes.get(storageUnitName);
+ Collection<String> actualTableNames =
each.getSchemaName().map(schemaName ->
schemaTableNames.getOrDefault(schemaName, Collections.emptySet()))
+ .orElseGet(() ->
getDefaultSchemaTableNames(schemaTableNames, defaultSchemaName,
storageUnitName, storageUnitDatabaseType,
+ aggregatedDataSourceMap.get(storageUnitName),
schemaPolicies));
+
ShardingSpherePreconditions.checkState(actualTableNames.contains(tableName), ()
-> new TableNotFoundException(tableName, storageUnitName));
}
}
}
+ private Collection<String> getDefaultSchemaTableNames(final Map<String,
Collection<String>> schemaTableNames, final String schemaName, final String
storageUnitName,
+ final DatabaseType
storageUnitDatabaseType, final DataSource dataSource,
+ final Map<String,
IdentifierCasePolicy> schemaPolicies) {
+ if (isSameDatabaseType(database.getProtocolType(),
storageUnitDatabaseType)) {
+ return schemaTableNames.getOrDefault(schemaName,
Collections.emptySet());
+ }
+ IdentifierCasePolicy schemaPolicy =
+ schemaPolicies.computeIfAbsent(storageUnitName, ignored ->
IdentifierNormalizeEngine.resolvePolicy(storageUnitDatabaseType, dataSource,
IdentifierScope.SCHEMA));
+ return
IdentifierNormalizeEngine.findMatchedIdentifier(schemaTableNames.keySet(),
schemaPolicy,
schemaName).map(schemaTableNames::get).orElseGet(Collections::emptySet);
+ }
+
private Map<String, Map<String, Collection<String>>>
getActualTableNodes(final Collection<String> storageUnitNames, final
Map<String, DataSource> aggregatedDataSourceMap,
final Map<String, DatabaseType> storageUnitDatabaseTypes) {
Map<String, Map<String, Collection<String>>> result = new
LinkedHashMap<>(storageUnitNames.size(), 1F);
diff --git
a/kernel/single/distsql/handler/src/test/java/org/apache/shardingsphere/single/distsql/handler/update/LoadSingleTableExecutorTest.java
b/kernel/single/distsql/handler/src/test/java/org/apache/shardingsphere/single/distsql/handler/update/LoadSingleTableExecutorTest.java
index a5bd95a4a35..610ed7eb3c3 100644
---
a/kernel/single/distsql/handler/src/test/java/org/apache/shardingsphere/single/distsql/handler/update/LoadSingleTableExecutorTest.java
+++
b/kernel/single/distsql/handler/src/test/java/org/apache/shardingsphere/single/distsql/handler/update/LoadSingleTableExecutorTest.java
@@ -143,8 +143,25 @@ class LoadSingleTableExecutorTest {
void assertCheckBeforeUpdateWithDifferentProtocolAndStorageTypes() {
when(DatabaseTypeEngine.getStorageType(any(DataSource.class))).thenReturn(TypedSPILoader.getService(DatabaseType.class,
"MySQL"));
prepareActualTableValidationScenario(Collections.singletonMap("foo_ds", new
MockedDataSource()), Collections.singletonMap("FOO_DB",
Collections.singleton("foo_tbl")));
- try (MockedConstruction<DatabaseTypeRegistry> ignored =
mockStorageDatabaseTypeRegistry()) {
- assertDoesNotThrow(() -> executor.checkBeforeUpdate(new
LoadSingleTableStatement(Collections.singletonList(new
SingleTableSegment("foo_ds", "foo_tbl")))));
+ assertDoesNotThrow(() -> executor.checkBeforeUpdate(new
LoadSingleTableStatement(Collections.singletonList(new
SingleTableSegment("foo_ds", "foo_tbl")))));
+ }
+
+ @Test
+ void
assertCheckBeforeUpdateWithExplicitSchemaAndDifferentProtocolAndStorageTypes() {
+
when(DatabaseTypeEngine.getStorageType(any(DataSource.class))).thenReturn(TypedSPILoader.getService(DatabaseType.class,
"MySQL"));
+
prepareActualTableValidationScenario(Collections.singletonMap("foo_ds", new
MockedDataSource()), Collections.singletonMap("foo_schema",
Collections.singleton("foo_tbl")));
+ try (MockedConstruction<DatabaseTypeRegistry> ignored =
mockSchemaSupportedDatabaseTypeRegistry()) {
+ assertDoesNotThrow(() -> executor.checkBeforeUpdate(new
LoadSingleTableStatement(Collections.singletonList(new
SingleTableSegment("foo_ds", "foo_schema", "foo_tbl")))));
+ }
+ }
+
+ @Test
+ void
assertCheckBeforeUpdateWithMismatchedExplicitSchemaAndDifferentProtocolAndStorageTypes()
{
+
when(DatabaseTypeEngine.getStorageType(any(DataSource.class))).thenReturn(TypedSPILoader.getService(DatabaseType.class,
"MySQL"));
+
prepareActualTableValidationScenario(Collections.singletonMap("foo_ds", new
MockedDataSource()), Collections.singletonMap("FOO_SCHEMA",
Collections.singleton("foo_tbl")));
+ try (MockedConstruction<DatabaseTypeRegistry> ignored =
mockSchemaSupportedDatabaseTypeRegistry()) {
+ assertThrows(TableNotFoundException.class,
+ () -> executor.checkBeforeUpdate(new
LoadSingleTableStatement(Collections.singletonList(new
SingleTableSegment("foo_ds", "foo_schema", "foo_tbl")))));
}
}
@@ -206,15 +223,6 @@ class LoadSingleTableExecutorTest {
});
}
- private MockedConstruction<DatabaseTypeRegistry>
mockStorageDatabaseTypeRegistry() {
- DialectDatabaseMetaData dialectDatabaseMetaData =
mock(DialectDatabaseMetaData.class, RETURNS_DEEP_STUBS);
- return mockConstruction(DatabaseTypeRegistry.class, (mock, context) ->
{
- when(mock.getDefaultSchemaName("foo_db")).thenReturn("foo_db");
- when(mock.formatIdentifierPattern("foo_db")).thenReturn("FOO_DB");
-
when(mock.getDialectDatabaseMetaData()).thenReturn(dialectDatabaseMetaData);
- });
- }
-
private static Stream<Arguments>
assertCheckBeforeUpdateWithPreValidationFailureArguments() {
return Stream.of(
Arguments.of("schema unsupported rejects schema name", false,
new SingleTableSegment("foo_ds", "foo_schema", "foo_tbl"), false,
InvalidDataNodeFormatException.class),