This is an automated email from the ASF dual-hosted git repository.
sandynz 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 63ab282c627 Preserve Identifier Case Across Migration Pipelines
(#39623)
63ab282c627 is described below
commit 63ab282c627d6262059276aa03532b6f3abaca56
Author: Hongsheng Zhong <[email protected]>
AuthorDate: Fri Aug 28 15:07:33 2026 +0800
Preserve Identifier Case Across Migration Pipelines (#39623)
* Preserve identifier case in Migration jobs
* Normalize source table identifiers in Migration jobs
* Preserve actual identifiers in pipeline E2E cleanup
* Preserve target identifier case in Migration jobs
* Add case-sensitive MySQL Migration E2E
* Use endpoint-specific identifiers in Migration checks
* Simplify table inventory check parameters
* Enable case-sensitive table names for MySQL 5.7 E2E
---
.../table/MatchingTableInventoryChecker.java | 2 +-
.../table/TableInventoryCheckParameter.java | 14 +-
.../ingest/incremental/wal/WALEventConverter.java | 36 +++++-
.../component/table/default/get_table_id.ftl | 2 +-
.../incremental/wal/WALEventConverterTest.java | 27 +++-
.../PostgreSQLPipelineFreemarkerManagerTest.java | 10 ++
.../migration/MigrationJobExecutorCallback.java | 50 ++++++--
.../MigrationDataConsistencyChecker.java | 21 ++-
.../MigrationJobExecutorCallbackTest.java | 87 ++++++++++++-
.../handler/update/MigrateTableExecutor.java | 24 ++--
.../handler/update/MigrateTableExecutorTest.java | 46 +++++--
.../core/MigrationDistSQLStatementVisitor.java | 17 +--
.../core/MigrationDistSQLStatementVisitorTest.java | 48 +++++++
.../segment/MigrationSourceTargetSegment.java | 58 ++++++++-
.../general/MySQLMigrationIdentifierCaseE2EIT.java | 114 +++++++++++++++++
.../PostgreSQLMigrationIdentifierCaseE2EIT.java | 142 +++++++++++++++++++++
.../natived/PipelineNativeContainerComposer.java | 5 +-
...eSQLPipelineNativeContainerDropTableOption.java | 2 +-
.../resources/env/container/mysql/cnf/5/my.cnf | 2 +-
.../resources/env/container/mysql/cnf/8/my.cnf | 2 +-
20 files changed, 648 insertions(+), 61 deletions(-)
diff --git
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/consistencycheck/table/MatchingTableInventoryChecker.java
b/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/consistencycheck/table/MatchingTableInventoryChecker.java
index d92c397660b..a2ae2586699 100644
---
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/consistencycheck/table/MatchingTableInventoryChecker.java
+++
b/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/consistencycheck/table/MatchingTableInventoryChecker.java
@@ -131,7 +131,7 @@ public abstract class MatchingTableInventoryChecker
implements TableInventoryChe
private TableInventoryCalculateParameter
getTableInventoryCalculateParameter(final TableInventoryCheckParameter param,
final TableCheckRangePosition checkRangePosition) {
TableInventoryCalculateParameter result = new
TableInventoryCalculateParameter(param.getTargetDataSource(),
param.getTargetTable(),
- param.getColumnNames(), param.getUniqueKeys(),
QueryType.RANGE_QUERY, param.getQueryCondition());
+ param.getTargetColumnNames(), param.getTargetUniqueKeys(),
QueryType.RANGE_QUERY, param.getQueryCondition());
result.setRange(Range.closed(null !=
checkRangePosition.getTargetPosition() ? checkRangePosition.getTargetPosition()
: checkRangePosition.getTargetRange().getLowerBound(),
checkRangePosition.getTargetRange().getUpperBound()));
return result;
diff --git
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/consistencycheck/table/TableInventoryCheckParameter.java
b/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/consistencycheck/table/TableInventoryCheckParameter.java
index 2b45ad445c0..5cb2a6e79eb 100644
---
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/consistencycheck/table/TableInventoryCheckParameter.java
+++
b/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/consistencycheck/table/TableInventoryCheckParameter.java
@@ -30,8 +30,8 @@ import java.util.List;
/**
* Table inventory check parameter.
*/
-@RequiredArgsConstructor
@Getter
+@RequiredArgsConstructor
public final class TableInventoryCheckParameter {
private final String jobId;
@@ -50,12 +50,24 @@ public final class TableInventoryCheckParameter {
private final List<PipelineColumnMetaData> uniqueKeys;
+ private final List<String> targetColumnNames;
+
+ private final List<PipelineColumnMetaData> targetUniqueKeys;
+
private final JobRateLimitAlgorithm readRateLimitAlgorithm;
private final ConsistencyCheckJobItemProgressContext progressContext;
private final String queryCondition;
+ public TableInventoryCheckParameter(final String jobId, final int
splittingItem, final PipelineDataSource sourceDataSource, final
PipelineDataSource targetDataSource,
+ final QualifiedTable sourceTable,
final QualifiedTable targetTable,
+ final List<String> columnNames, final
List<PipelineColumnMetaData> uniqueKeys,
+ final JobRateLimitAlgorithm
readRateLimitAlgorithm, final ConsistencyCheckJobItemProgressContext
progressContext, final String queryCondition) {
+ this(jobId, splittingItem, sourceDataSource, targetDataSource,
sourceTable, targetTable, columnNames, uniqueKeys, columnNames, uniqueKeys,
+ readRateLimitAlgorithm, progressContext, queryCondition);
+ }
+
public TableInventoryCheckParameter(final String jobId, final
PipelineDataSource sourceDataSource, final PipelineDataSource targetDataSource,
final QualifiedTable sourceTable,
final QualifiedTable targetTable,
final List<String> columnNames, final
List<PipelineColumnMetaData> uniqueKeys,
diff --git
a/kernel/data-pipeline/dialect/postgresql/src/main/java/org/apache/shardingsphere/data/pipeline/postgresql/ingest/incremental/wal/WALEventConverter.java
b/kernel/data-pipeline/dialect/postgresql/src/main/java/org/apache/shardingsphere/data/pipeline/postgresql/ingest/incremental/wal/WALEventConverter.java
index 61153669589..4f7209fa27a 100644
---
a/kernel/data-pipeline/dialect/postgresql/src/main/java/org/apache/shardingsphere/data/pipeline/postgresql/ingest/incremental/wal/WALEventConverter.java
+++
b/kernel/data-pipeline/dialect/postgresql/src/main/java/org/apache/shardingsphere/data/pipeline/postgresql/ingest/incremental/wal/WALEventConverter.java
@@ -36,10 +36,14 @@ import
org.apache.shardingsphere.database.connector.core.metadata.database.enums
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.metadata.identifier.LookupMode;
import
org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException;
import
org.apache.shardingsphere.infra.metadata.identifier.ShardingSphereIdentifier;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
/**
* WAL event converter.
@@ -52,6 +56,12 @@ public final class WALEventConverter {
private final IdentifierCasePolicy schemaIdentifierCasePolicy;
+ private final IdentifierCasePolicy tableIdentifierCasePolicy;
+
+ private final Map<String, ShardingSphereIdentifier> quotedTableNameMap;
+
+ private final Map<String, ShardingSphereIdentifier> unquotedTableNameMap;
+
/**
* Create a WAL event converter.
*
@@ -63,6 +73,18 @@ public final class WALEventConverter {
this.metaDataLoader = metaDataLoader;
schemaIdentifierCasePolicy = IdentifierNormalizeEngine.resolvePolicy(
dumperContext.getCommonContext().getDataSourceConfig().getDatabaseType(), null,
IdentifierScope.SCHEMA);
+ tableIdentifierCasePolicy = IdentifierNormalizeEngine.resolvePolicy(
+
dumperContext.getCommonContext().getDataSourceConfig().getDatabaseType(), null,
IdentifierScope.TABLE);
+ Map<ShardingSphereIdentifier, ShardingSphereIdentifier> tableNameMap =
dumperContext.getCommonContext().getTableNameMapper().getTableNameMap();
+ quotedTableNameMap = new HashMap<>(tableNameMap.size(), 1F);
+ unquotedTableNameMap = new HashMap<>(tableNameMap.size(), 1F);
+ for (Entry<ShardingSphereIdentifier, ShardingSphereIdentifier> entry :
tableNameMap.entrySet()) {
+ String actualTableName = entry.getKey().getValue();
+ quotedTableNameMap.put(getLookupKey(actualTableName,
QuoteCharacter.QUOTE), entry.getValue());
+ if (tableIdentifierCasePolicy.matches(actualTableName,
actualTableName, QuoteCharacter.NONE)) {
+ unquotedTableNameMap.put(getLookupKey(actualTableName,
QuoteCharacter.NONE), entry.getValue());
+ }
+ }
}
/**
@@ -77,8 +99,9 @@ public final class WALEventConverter {
return createPlaceholderRecord(event);
}
AbstractRowEvent rowEvent = (AbstractRowEvent) event;
- String actualTableName = rowEvent.getTableName();
- ShardingSphereIdentifier logicTableName =
dumperContext.getCommonContext().getTableNameMapper().getLogicTableName(actualTableName);
+ QuoteCharacter tableQuoteCharacter =
QuoteCharacter.getQuoteCharacter(rowEvent.getTableName());
+ String actualTableName =
tableQuoteCharacter.unwrap(rowEvent.getTableName());
+ ShardingSphereIdentifier logicTableName =
getLogicTableName(actualTableName, tableQuoteCharacter);
if (null == logicTableName) {
return createPlaceholderRecord(event);
}
@@ -99,6 +122,15 @@ public final class WALEventConverter {
throw new UnsupportedSQLOperationException("");
}
+ private ShardingSphereIdentifier getLogicTableName(final String
actualTableName, final QuoteCharacter quoteCharacter) {
+ Map<String, ShardingSphereIdentifier> tableNameMap =
QuoteCharacter.NONE == quoteCharacter ? unquotedTableNameMap :
quotedTableNameMap;
+ return tableNameMap.get(getLookupKey(actualTableName, quoteCharacter));
+ }
+
+ private String getLookupKey(final String identifier, final QuoteCharacter
quoteCharacter) {
+ return LookupMode.EXACT ==
tableIdentifierCasePolicy.getLookupMode(quoteCharacter) ? identifier :
tableIdentifierCasePolicy.normalizeForLookup(identifier);
+ }
+
private boolean isDifferentSchemaName(final AbstractRowEvent event, final
String expectedSchemaName) {
if (!isConcreteSchemaName(expectedSchemaName)) {
return false;
diff --git
a/kernel/data-pipeline/dialect/postgresql/src/main/resources/template/component/table/default/get_table_id.ftl
b/kernel/data-pipeline/dialect/postgresql/src/main/resources/template/component/table/default/get_table_id.ftl
index 44578d29dea..ccd20324e10 100644
---
a/kernel/data-pipeline/dialect/postgresql/src/main/resources/template/component/table/default/get_table_id.ftl
+++
b/kernel/data-pipeline/dialect/postgresql/src/main/resources/template/component/table/default/get_table_id.ftl
@@ -15,4 +15,4 @@
~ limitations under the License.
-->
-SELECT '${schemaName}.${tableName}'::REGCLASS::OID AS tid;
+SELECT (pg_catalog.quote_ident('${schemaName}') || '.' ||
pg_catalog.quote_ident('${tableName}'))::REGCLASS::OID AS tid;
diff --git
a/kernel/data-pipeline/dialect/postgresql/src/test/java/org/apache/shardingsphere/data/pipeline/postgresql/ingest/incremental/wal/WALEventConverterTest.java
b/kernel/data-pipeline/dialect/postgresql/src/test/java/org/apache/shardingsphere/data/pipeline/postgresql/ingest/incremental/wal/WALEventConverterTest.java
index 13e40c965c9..99bb889d291 100644
---
a/kernel/data-pipeline/dialect/postgresql/src/test/java/org/apache/shardingsphere/data/pipeline/postgresql/ingest/incremental/wal/WALEventConverterTest.java
+++
b/kernel/data-pipeline/dialect/postgresql/src/test/java/org/apache/shardingsphere/data/pipeline/postgresql/ingest/incremental/wal/WALEventConverterTest.java
@@ -203,6 +203,27 @@ class WALEventConverterTest {
assertThat(walEventConverter.convert(mockUnknownTableEvent()),
isA(PlaceholderRecord.class));
}
+ @Test
+ void assertConvertWithQuotedUpperCaseTable() {
+ PipelineTableMetaDataLoader metaDataLoader =
mock(PipelineTableMetaDataLoader.class);
+ when(metaDataLoader.getTableMetaData("UPPER_SCHEMA",
"UPPER_TABLE")).thenReturn(pipelineTableMetaData);
+ WALEventConverter converter =
createSchemaAwareWALEventConverter("UPPER_TABLE", "UPPER_SCHEMA",
metaDataLoader);
+ WriteRowEvent event = createWriteRowEvent("\"UPPER_SCHEMA\"");
+ event.setTableName("\"UPPER_TABLE\"");
+ assertThat(converter.convert(event), isA(DataRecord.class));
+ verify(metaDataLoader).getTableMetaData("UPPER_SCHEMA", "UPPER_TABLE");
+ }
+
+ @Test
+ void assertConvertWithQuotedTableCaseCollision() {
+ PipelineTableMetaDataLoader metaDataLoader =
mock(PipelineTableMetaDataLoader.class);
+ WALEventConverter converter =
createSchemaAwareWALEventConverter("UPPER_TABLE", "UPPER_SCHEMA",
metaDataLoader);
+ WriteRowEvent event = createWriteRowEvent("\"UPPER_SCHEMA\"");
+ event.setTableName("\"upper_table\"");
+ assertThat(converter.convert(event), isA(PlaceholderRecord.class));
+ verifyNoInteractions(metaDataLoader);
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("provideDifferentConcreteSchemaNames")
void assertConvertWithDifferentConcreteSchema(final String name, final
String expectedSchemaName, final String eventSchemaName) {
@@ -263,10 +284,14 @@ class WALEventConverterTest {
}
private WALEventConverter createSchemaAwareWALEventConverter(final String
expectedSchemaName, final PipelineTableMetaDataLoader metaDataLoader) {
+ return createSchemaAwareWALEventConverter("t_order",
expectedSchemaName, metaDataLoader);
+ }
+
+ private WALEventConverter createSchemaAwareWALEventConverter(final String
actualTableName, final String expectedSchemaName, final
PipelineTableMetaDataLoader metaDataLoader) {
PipelineDataSourceConfiguration dataSourceConfig =
mock(PipelineDataSourceConfiguration.class);
when(dataSourceConfig.getDatabaseType()).thenReturn(databaseType);
DumperCommonContext commonContext = new DumperCommonContext(null,
dataSourceConfig,
- new ActualAndLogicTableNameMapper(Collections.singletonMap(new
ShardingSphereIdentifier("t_order"), new ShardingSphereIdentifier("t_order"))),
+ new ActualAndLogicTableNameMapper(Collections.singletonMap(new
ShardingSphereIdentifier(actualTableName), new
ShardingSphereIdentifier("t_order"))),
new
TableAndSchemaNameMapper(Collections.singletonMap("t_order",
expectedSchemaName)));
return new WALEventConverter(new
IncrementalDumperContext(commonContext, null, false), metaDataLoader);
}
diff --git
a/kernel/data-pipeline/dialect/postgresql/src/test/java/org/apache/shardingsphere/data/pipeline/postgresql/sqlbuilder/template/PostgreSQLPipelineFreemarkerManagerTest.java
b/kernel/data-pipeline/dialect/postgresql/src/test/java/org/apache/shardingsphere/data/pipeline/postgresql/sqlbuilder/template/PostgreSQLPipelineFreemarkerManagerTest.java
index a3606b2d552..7d8d53c34d8 100644
---
a/kernel/data-pipeline/dialect/postgresql/src/test/java/org/apache/shardingsphere/data/pipeline/postgresql/sqlbuilder/template/PostgreSQLPipelineFreemarkerManagerTest.java
+++
b/kernel/data-pipeline/dialect/postgresql/src/test/java/org/apache/shardingsphere/data/pipeline/postgresql/sqlbuilder/template/PostgreSQLPipelineFreemarkerManagerTest.java
@@ -48,6 +48,16 @@ class PostgreSQLPipelineFreemarkerManagerTest {
assertNotNull(PostgreSQLPipelineFreemarkerManager.getSQLByVersion(dataModel,
"component/table/%s/get_columns_for_table.ftl", 10, 0));
}
+ @Test
+ void assertGetTableIdSQLWithActualIdentifiers() {
+ Map<String, Object> dataModel = new HashMap<>(2, 1F);
+ dataModel.put("schemaName", "UPPER_SCHEMA");
+ dataModel.put("tableName", "UPPER_TABLE");
+ String actual =
PostgreSQLPipelineFreemarkerManager.getSQLByVersion(dataModel,
"component/table/%s/get_table_id.ftl", 12, 0);
+ String expected = "\nSELECT (pg_catalog.quote_ident('UPPER_SCHEMA') ||
'.' || pg_catalog.quote_ident('UPPER_TABLE'))::REGCLASS::OID AS tid;\n";
+ assertThat(actual, is(expected));
+ }
+
@Test
void assertCreateTableTemplateRendersNormalizedSequenceNumbers() {
Map<String, Object> dataModel = new LinkedHashMap<>(8, 1F);
diff --git
a/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/MigrationJobExecutorCallback.java
b/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/MigrationJobExecutorCallback.java
index 578edf19537..846fe9ec3e3 100644
---
a/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/MigrationJobExecutorCallback.java
+++
b/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/MigrationJobExecutorCallback.java
@@ -22,6 +22,7 @@ import
org.apache.shardingsphere.data.pipeline.api.type.ShardingSpherePipelineDa
import
org.apache.shardingsphere.data.pipeline.core.context.PipelineContextManager;
import
org.apache.shardingsphere.data.pipeline.core.context.TransmissionProcessContext;
import org.apache.shardingsphere.data.pipeline.core.datanode.JobDataNodeEntry;
+import
org.apache.shardingsphere.data.pipeline.core.datasource.PipelineDataSource;
import
org.apache.shardingsphere.data.pipeline.core.datasource.PipelineDataSourceManager;
import
org.apache.shardingsphere.data.pipeline.core.importer.ImporterConfiguration;
import
org.apache.shardingsphere.data.pipeline.core.importer.PipelineRequiredColumnsExtractor;
@@ -42,6 +43,9 @@ import
org.apache.shardingsphere.data.pipeline.scenario.migration.config.Migrati
import
org.apache.shardingsphere.data.pipeline.scenario.migration.context.MigrationJobItemContext;
import
org.apache.shardingsphere.data.pipeline.scenario.migration.ingest.dumper.MigrationIncrementalDumperContextCreator;
import
org.apache.shardingsphere.data.pipeline.scenario.migration.preparer.MigrationJobPreparer;
+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.DatabaseTypeRegistry;
import org.apache.shardingsphere.infra.datanode.DataNode;
import
org.apache.shardingsphere.infra.datasource.pool.props.domain.DataSourcePoolProperties;
@@ -53,7 +57,9 @@ import
org.apache.shardingsphere.infra.yaml.config.pojo.rule.YamlRuleConfigurati
import java.sql.SQLException;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
@@ -70,7 +76,7 @@ public final class MigrationJobExecutorCallback implements
DistributedPipelineJo
final
TransmissionJobItemProgress jobItemProgress, final TransmissionProcessContext
jobProcessContext,
final
PipelineDataSourceManager dataSourceManager) {
transformDataSourceConfigurations(jobConfig);
- MigrationTaskConfiguration taskConfig =
buildTaskConfiguration(jobConfig, shardingItem,
jobProcessContext.getProcessConfiguration());
+ MigrationTaskConfiguration taskConfig =
buildTaskConfiguration(jobConfig, shardingItem,
jobProcessContext.getProcessConfiguration(), dataSourceManager);
return new MigrationJobItemContext(jobConfig, shardingItem,
jobItemProgress, jobProcessContext, taskConfig, dataSourceManager);
}
@@ -90,15 +96,33 @@ public final class MigrationJobExecutorCallback implements
DistributedPipelineJo
: entry.getValue();
}
- private MigrationTaskConfiguration buildTaskConfiguration(final
MigrationJobConfiguration jobConfig, final int jobShardingItem, final
PipelineProcessConfiguration processConfig) {
+ private MigrationTaskConfiguration buildTaskConfiguration(final
MigrationJobConfiguration jobConfig, final int jobShardingItem, final
PipelineProcessConfiguration processConfig,
+ final
PipelineDataSourceManager dataSourceManager) {
Map<ShardingSphereIdentifier, Collection<String>>
tableAndRequiredColumnsMap = getTableAndRequiredColumnsMap(jobConfig);
IncrementalDumperContext incrementalDumperContext = new
MigrationIncrementalDumperContextCreator(jobConfig).createDumperContext(jobConfig.getJobDataNodeLine(jobShardingItem));
- Collection<CreateTableConfiguration> createTableConfigs =
buildCreateTableConfigurations(jobConfig,
incrementalDumperContext.getCommonContext().getTableAndSchemaNameMapper());
+ PipelineDataSource targetDataSource =
dataSourceManager.getDataSource(jobConfig.getTarget());
+ IdentifierCasePolicy targetSchemaIdentifierPolicy =
IdentifierNormalizeEngine.resolvePolicy(jobConfig.getTargetDatabaseType(),
targetDataSource, IdentifierScope.SCHEMA);
+ IdentifierCasePolicy targetTableIdentifierPolicy =
IdentifierNormalizeEngine.resolvePolicy(jobConfig.getTargetDatabaseType(),
targetDataSource, IdentifierScope.TABLE);
+ Collection<CreateTableConfiguration> createTableConfigs =
buildCreateTableConfigurations(
+ jobConfig,
incrementalDumperContext.getCommonContext().getTableAndSchemaNameMapper(),
targetSchemaIdentifierPolicy, targetTableIdentifierPolicy);
ImporterConfiguration importerConfig = buildImporterConfiguration(
- jobConfig, processConfig, tableAndRequiredColumnsMap,
incrementalDumperContext.getCommonContext().getTableAndSchemaNameMapper());
+ jobConfig, processConfig, tableAndRequiredColumnsMap,
getTargetTableAndSchemaNameMapper(jobConfig, targetSchemaIdentifierPolicy,
targetTableIdentifierPolicy));
return new
MigrationTaskConfiguration(incrementalDumperContext.getCommonContext().getDataSourceName(),
createTableConfigs, incrementalDumperContext, importerConfig);
}
+ private TableAndSchemaNameMapper getTargetTableAndSchemaNameMapper(final
MigrationJobConfiguration jobConfig, final IdentifierCasePolicy
targetSchemaIdentifierPolicy,
+ final
IdentifierCasePolicy targetTableIdentifierPolicy) {
+ if (null == jobConfig.getTargetTableSchemaMap()) {
+ return new TableAndSchemaNameMapper(Collections.emptyMap());
+ }
+ Map<String, String> targetTableSchemaMap = new
LinkedHashMap<>(jobConfig.getTargetTableSchemaMap().size(), 1F);
+ for (Entry<String, String> entry :
jobConfig.getTargetTableSchemaMap().entrySet()) {
+ String schemaName = null == entry.getValue() ? null :
IdentifierNormalizeEngine.normalize(targetSchemaIdentifierPolicy,
entry.getValue());
+
targetTableSchemaMap.put(IdentifierNormalizeEngine.normalize(targetTableIdentifierPolicy,
entry.getKey()), schemaName);
+ }
+ return new TableAndSchemaNameMapper(targetTableSchemaMap);
+ }
+
@SuppressWarnings({"rawtypes", "unchecked"})
private Map<ShardingSphereIdentifier, Collection<String>>
getTableAndRequiredColumnsMap(final MigrationJobConfiguration jobConfig) {
Map<ShardingSphereIdentifier, Collection<String>> result = new
HashMap<>();
@@ -110,17 +134,25 @@ public final class MigrationJobExecutorCallback
implements DistributedPipelineJo
return result;
}
- private Collection<CreateTableConfiguration>
buildCreateTableConfigurations(final MigrationJobConfiguration jobConfig, final
TableAndSchemaNameMapper mapper) {
- return
jobConfig.getTablesFirstDataNodes().getEntries().stream().map(each ->
getCreateTableConfiguration(jobConfig, mapper,
each)).collect(Collectors.toList());
+ private Collection<CreateTableConfiguration>
buildCreateTableConfigurations(final MigrationJobConfiguration jobConfig, final
TableAndSchemaNameMapper mapper,
+
final IdentifierCasePolicy targetSchemaIdentifierPolicy,
+
final IdentifierCasePolicy targetTableIdentifierPolicy) {
+ return jobConfig.getTablesFirstDataNodes().getEntries().stream()
+ .map(each -> getCreateTableConfiguration(jobConfig, mapper,
each, targetSchemaIdentifierPolicy,
targetTableIdentifierPolicy)).collect(Collectors.toList());
}
- private CreateTableConfiguration getCreateTableConfiguration(final
MigrationJobConfiguration jobConfig, final TableAndSchemaNameMapper mapper,
final JobDataNodeEntry jobDataNodeEntry) {
+ private CreateTableConfiguration getCreateTableConfiguration(final
MigrationJobConfiguration jobConfig, final TableAndSchemaNameMapper mapper,
+ final
JobDataNodeEntry jobDataNodeEntry, final IdentifierCasePolicy
targetSchemaIdentifierPolicy,
+ final
IdentifierCasePolicy targetTableIdentifierPolicy) {
DataNode dataNode = jobDataNodeEntry.getDataNodes().get(0);
PipelineDataSourceConfiguration sourceDataSourceConfig =
jobConfig.getSources().get(dataNode.getDataSourceName());
String sourceSchemaName =
mapper.getSchemaName(jobDataNodeEntry.getLogicTableName());
- String targetSchemaName = new
DatabaseTypeRegistry(jobConfig.getTargetDatabaseType()).getDialectDatabaseMetaData().getSchemaOption().isSchemaAvailable()
? sourceSchemaName : null;
+ String targetSchemaName = null != sourceSchemaName && new
DatabaseTypeRegistry(jobConfig.getTargetDatabaseType()).getDialectDatabaseMetaData().getSchemaOption().isSchemaAvailable()
+ ?
IdentifierNormalizeEngine.normalize(targetSchemaIdentifierPolicy,
sourceSchemaName)
+ : null;
+ String targetTableName =
IdentifierNormalizeEngine.normalize(targetTableIdentifierPolicy,
jobDataNodeEntry.getLogicTableName());
return new CreateTableConfiguration(sourceDataSourceConfig, new
QualifiedTable(sourceSchemaName, dataNode.getTableName()),
- jobConfig.getTarget(), new QualifiedTable(targetSchemaName,
jobDataNodeEntry.getLogicTableName()));
+ jobConfig.getTarget(), new QualifiedTable(targetSchemaName,
targetTableName));
}
private ImporterConfiguration buildImporterConfiguration(final
MigrationJobConfiguration jobConfig, final PipelineProcessConfiguration
pipelineProcessConfig,
diff --git
a/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/check/consistency/MigrationDataConsistencyChecker.java
b/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/check/consistency/MigrationDataConsistencyChecker.java
index 27af07ec36e..28a4db3c937 100644
---
a/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/check/consistency/MigrationDataConsistencyChecker.java
+++
b/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/check/consistency/MigrationDataConsistencyChecker.java
@@ -50,6 +50,10 @@ import
org.apache.shardingsphere.data.pipeline.core.ratelimit.JobRateLimitAlgori
import
org.apache.shardingsphere.data.pipeline.core.util.PipelineDataSourceConfigurationUtils;
import
org.apache.shardingsphere.data.pipeline.scenario.migration.MigrationJobType;
import
org.apache.shardingsphere.data.pipeline.scenario.migration.config.MigrationJobConfiguration;
+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.DatabaseTypeRegistry;
import org.apache.shardingsphere.infra.datanode.DataNode;
import
org.apache.shardingsphere.infra.datasource.pool.props.domain.DataSourcePoolProperties;
import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
@@ -174,11 +178,22 @@ public final class MigrationDataConsistencyChecker
implements PipelineDataConsis
String targetTableName = checkRangePosition.getLogicTableName();
List<String> columnNames = tableMetaData.getColumnNames();
List<PipelineColumnMetaData> uniqueKeys =
PipelineTableMetaDataUtils.getUniqueKeyColumns(sourceTable.getSchemaName(),
sourceTable.getTableName(), metaDataLoader);
- QualifiedTable targetTable = new
QualifiedTable(dataNode.getSchemaName(), targetTableName);
PipelineDataSource targetDataSource =
dataSourceManager.getDataSource(jobConfig.getTarget());
+ IdentifierCasePolicy targetSchemaIdentifierPolicy =
IdentifierNormalizeEngine.resolvePolicy(jobConfig.getTargetDatabaseType(),
targetDataSource, IdentifierScope.SCHEMA);
+ IdentifierCasePolicy targetTableIdentifierPolicy =
IdentifierNormalizeEngine.resolvePolicy(jobConfig.getTargetDatabaseType(),
targetDataSource, IdentifierScope.TABLE);
+ IdentifierCasePolicy targetColumnIdentifierPolicy =
IdentifierNormalizeEngine.resolvePolicy(jobConfig.getTargetDatabaseType(),
targetDataSource, IdentifierScope.COLUMN);
+ String targetSchemaName = null != dataNode.getSchemaName()
+ && new
DatabaseTypeRegistry(jobConfig.getTargetDatabaseType()).getDialectDatabaseMetaData().getSchemaOption().isSchemaAvailable()
+ ?
IdentifierNormalizeEngine.normalize(targetSchemaIdentifierPolicy,
dataNode.getSchemaName())
+ : null;
+ QualifiedTable targetTable = new QualifiedTable(targetSchemaName,
IdentifierNormalizeEngine.normalize(targetTableIdentifierPolicy,
targetTableName));
+ List<String> targetColumnNames = columnNames.stream().map(each ->
IdentifierNormalizeEngine.normalize(targetColumnIdentifierPolicy,
each)).collect(Collectors.toList());
+ List<PipelineColumnMetaData> targetUniqueKeys =
uniqueKeys.stream().map(each -> new
PipelineColumnMetaData(each.getOrdinalPosition(),
+
IdentifierNormalizeEngine.normalize(targetColumnIdentifierPolicy,
each.getName()), each.getDataType(), each.getDataTypeName(),
+ each.isNullable(), each.isPrimaryKey(),
each.isUniqueKey())).collect(Collectors.toList());
TableInventoryCheckParameter param = new TableInventoryCheckParameter(
- jobConfig.getJobId(), checkRangePosition.getSplittingItem(),
sourceDataSource, targetDataSource, sourceTable, targetTable, columnNames,
uniqueKeys,
- readRateLimitAlgorithm, progressContext,
checkRangePosition.getQueryCondition());
+ jobConfig.getJobId(), checkRangePosition.getSplittingItem(),
sourceDataSource, targetDataSource, sourceTable, targetTable,
+ columnNames, uniqueKeys, targetColumnNames, targetUniqueKeys,
readRateLimitAlgorithm, progressContext,
checkRangePosition.getQueryCondition());
TableInventoryChecker tableInventoryChecker =
tableChecker.buildTableInventoryChecker(param);
currentTableInventoryChecker.set(tableInventoryChecker);
Optional<TableDataConsistencyCheckResult> preCheckResult =
tableInventoryChecker.preCheck();
diff --git
a/kernel/data-pipeline/scenario/migration/core/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/MigrationJobExecutorCallbackTest.java
b/kernel/data-pipeline/scenario/migration/core/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/MigrationJobExecutorCallbackTest.java
index 1d826b5cdd4..17fd896d23c 100644
---
a/kernel/data-pipeline/scenario/migration/core/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/MigrationJobExecutorCallbackTest.java
+++
b/kernel/data-pipeline/scenario/migration/core/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/MigrationJobExecutorCallbackTest.java
@@ -25,6 +25,7 @@ import
org.apache.shardingsphere.data.pipeline.core.context.PipelineContextManag
import
org.apache.shardingsphere.data.pipeline.core.context.TransmissionProcessContext;
import org.apache.shardingsphere.data.pipeline.core.datanode.JobDataNodeEntry;
import org.apache.shardingsphere.data.pipeline.core.datanode.JobDataNodeLine;
+import
org.apache.shardingsphere.data.pipeline.core.datasource.PipelineDataSource;
import
org.apache.shardingsphere.data.pipeline.core.datasource.PipelineDataSourceManager;
import
org.apache.shardingsphere.data.pipeline.core.importer.ImporterConfiguration;
import org.apache.shardingsphere.data.pipeline.core.job.api.PipelineAPIFactory;
@@ -37,16 +38,22 @@ import
org.apache.shardingsphere.data.pipeline.core.registrycenter.repository.Pi
import
org.apache.shardingsphere.data.pipeline.scenario.migration.config.MigrationJobConfiguration;
import
org.apache.shardingsphere.data.pipeline.scenario.migration.config.MigrationTaskConfiguration;
import
org.apache.shardingsphere.data.pipeline.scenario.migration.context.MigrationJobItemContext;
+import
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierCasePolicyFactory;
+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.DatabaseTypeFactory;
+import
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
import org.apache.shardingsphere.infra.datanode.DataNode;
import
org.apache.shardingsphere.infra.datasource.pool.props.domain.DataSourcePoolProperties;
import org.apache.shardingsphere.infra.instance.metadata.InstanceType;
import
org.apache.shardingsphere.infra.metadata.database.resource.unit.StorageUnit;
+import org.apache.shardingsphere.infra.metadata.database.schema.QualifiedTable;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.infra.yaml.config.pojo.YamlRootConfiguration;
import org.apache.shardingsphere.mode.manager.ContextManager;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import java.util.Collection;
@@ -58,9 +65,15 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.withSettings;
import static org.mockito.Mockito.when;
class MigrationJobExecutorCallbackTest {
@@ -122,6 +135,52 @@ class MigrationJobExecutorCallbackTest {
}
}
+ @Test
+ void assertBuildJobItemContextWithPostgreSQLTargetIdentifiers() {
+ try (
+ MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class,
withSettings().defaultAnswer(RETURNS_DEEP_STUBS),
+ (mock, context) ->
when(mock.getDialectDatabaseMetaData().getSchemaOption().isSchemaAvailable()).thenReturn(true));
+ MockedStatic<DatabaseTypeFactory> databaseTypeFactory =
mockStatic(DatabaseTypeFactory.class);
+ MockedStatic<PipelineAPIFactory> pipelineAPIFactory =
mockStatic(PipelineAPIFactory.class);
+ MockedStatic<PipelineContextManager> pipelineContextManager =
mockStatic(PipelineContextManager.class)) {
+ mockDatabaseTypeFactory(databaseTypeFactory);
+ mockGovernanceFacade(pipelineAPIFactory,
createSourceDataSourceYaml(10, 20));
+ mockProxyContext(pipelineContextManager);
+ MigrationJobItemContext actual = new
MigrationJobExecutorCallback().buildJobItemContext(
+ createPostgreSQLJobConfiguration(), 0, null,
mockProcessContext(), mock(PipelineDataSourceManager.class));
+ CreateTableConfiguration actualCreateTableConfig =
actual.getTaskConfig().getCreateTableConfigurations().iterator().next();
+ assertThat(actualCreateTableConfig.getSourceName(), is(new
QualifiedTable("UPPER_SCHEMA", "UPPER_TABLE")));
+ assertThat(actualCreateTableConfig.getTargetName(), is(new
QualifiedTable("upper_schema", "t_order")));
+
assertThat(actual.getTaskConfig().getImporterConfig().getTableAndSchemaNameMapper().getQualifiedTables(),
+ is(Collections.singletonList(new
QualifiedTable("upper_schema", "t_order"))));
+ }
+ }
+
+ @Test
+ void assertBuildJobItemContextWithCaseSensitiveMySQLTargetIdentifier() {
+ PipelineDataSource targetDataSource = mock(PipelineDataSource.class);
+ PipelineDataSourceManager dataSourceManager =
mock(PipelineDataSourceManager.class);
+
when(dataSourceManager.getDataSource(any())).thenReturn(targetDataSource);
+ try (
+ MockedStatic<DatabaseTypeFactory> databaseTypeFactory =
mockStatic(DatabaseTypeFactory.class);
+ MockedStatic<PipelineAPIFactory> pipelineAPIFactory =
mockStatic(PipelineAPIFactory.class);
+ MockedStatic<PipelineContextManager> pipelineContextManager =
mockStatic(PipelineContextManager.class);
+ MockedStatic<IdentifierNormalizeEngine> normalizeEngine =
mockStatic(IdentifierNormalizeEngine.class,
withSettings().defaultAnswer(CALLS_REAL_METHODS))) {
+ mockDatabaseTypeFactory(databaseTypeFactory);
+ mockGovernanceFacade(pipelineAPIFactory,
createSourceDataSourceYaml(10, 20));
+ mockProxyContext(pipelineContextManager);
+ normalizeEngine.when(() ->
IdentifierNormalizeEngine.resolvePolicy(any(DatabaseType.class), isNull(),
eq(IdentifierScope.TABLE)))
+
.thenReturn(IdentifierCasePolicyFactory.newLowerCaseInsensitivePolicySet().getPolicy(IdentifierScope.TABLE));
+ normalizeEngine.when(() ->
IdentifierNormalizeEngine.resolvePolicy(any(DatabaseType.class),
same(targetDataSource), eq(IdentifierScope.TABLE)))
+
.thenReturn(IdentifierCasePolicyFactory.newSensitivePolicySet().getPolicy(IdentifierScope.TABLE));
+ MigrationJobItemContext actual = new
MigrationJobExecutorCallback().buildJobItemContext(
+ createMySQLJobConfiguration(), 0, null,
mockProcessContext(), dataSourceManager);
+ CreateTableConfiguration actualCreateTableConfig =
actual.getTaskConfig().getCreateTableConfigurations().iterator().next();
+ assertThat(actualCreateTableConfig.getTargetName(), is(new
QualifiedTable(null, "T_ORDER")));
+ normalizeEngine.verify(() ->
IdentifierNormalizeEngine.resolvePolicy(any(DatabaseType.class),
same(targetDataSource), eq(IdentifierScope.TABLE)));
+ }
+ }
+
private void mockDatabaseTypeFactory(final
MockedStatic<DatabaseTypeFactory> databaseTypeFactory) {
databaseTypeFactory.when(() ->
DatabaseTypeFactory.get(anyString())).thenReturn(databaseType);
}
@@ -164,11 +223,35 @@ class MigrationJobExecutorCallbackTest {
sources.put(SOURCE_DATA_SOURCE_NAME, new
StandardPipelineDataSourceConfiguration(createDataSourceProperties(SOURCE_JDBC_URL,
2, 3)));
return new MigrationJobConfiguration(createJobId(), DATABASE_NAME,
databaseType, databaseType, sources,
new
ShardingSpherePipelineDataSourceConfiguration(createRootConfiguration(4, 5)),
Collections.singletonList("t_order"),
- Collections.singletonMap("t_order", "foo_schema"),
createJobDataNodeLine(), Collections.singletonList(createJobDataNodeLine()), 1,
3);
+ null, createJobDataNodeLine(),
Collections.singletonList(createJobDataNodeLine()), 1, 3);
+ }
+
+ private MigrationJobConfiguration createPostgreSQLJobConfiguration() {
+ JobDataNodeLine jobDataNodeLine = new
JobDataNodeLine(Collections.singletonList(
+ new JobDataNodeEntry("T_ORDER", Collections.singletonList(new
DataNode(SOURCE_DATA_SOURCE_NAME, "UPPER_SCHEMA", "UPPER_TABLE")))));
+ Map<String, PipelineDataSourceConfiguration> sources = new
LinkedHashMap<>(1, 1F);
+ sources.put(SOURCE_DATA_SOURCE_NAME, new
StandardPipelineDataSourceConfiguration(createDataSourceProperties(SOURCE_JDBC_URL,
2, 3)));
+ return new MigrationJobConfiguration(createJobId(jobDataNodeLine),
DATABASE_NAME, databaseType, TypedSPILoader.getService(DatabaseType.class,
"PostgreSQL"), sources,
+ new
ShardingSpherePipelineDataSourceConfiguration(createRootConfiguration(4, 5)),
Collections.singletonList("T_ORDER"),
+ Collections.singletonMap("T_ORDER", "UPPER_SCHEMA"),
jobDataNodeLine, Collections.singletonList(jobDataNodeLine), 1, 3);
+ }
+
+ private MigrationJobConfiguration createMySQLJobConfiguration() {
+ JobDataNodeLine jobDataNodeLine = new
JobDataNodeLine(Collections.singletonList(
+ new JobDataNodeEntry("T_ORDER", Collections.singletonList(new
DataNode(SOURCE_DATA_SOURCE_NAME + ".UPPER_TABLE")))));
+ Map<String, PipelineDataSourceConfiguration> sources = new
LinkedHashMap<>(1, 1F);
+ sources.put(SOURCE_DATA_SOURCE_NAME, new
StandardPipelineDataSourceConfiguration(createDataSourceProperties(SOURCE_JDBC_URL,
2, 3)));
+ return new MigrationJobConfiguration(createJobId(jobDataNodeLine),
DATABASE_NAME, databaseType, TypedSPILoader.getService(DatabaseType.class,
"MySQL"), sources,
+ new
ShardingSpherePipelineDataSourceConfiguration(createRootConfiguration(4, 5)),
Collections.singletonList("T_ORDER"),
+ Collections.singletonMap("T_ORDER", null), jobDataNodeLine,
Collections.singletonList(jobDataNodeLine), 1, 3);
}
private String createJobId() {
- return PipelineJobIdUtils.marshal(new MigrationJobId(new
PipelineContextKey(InstanceType.PROXY),
Collections.singletonList(createJobDataNodeLine().marshal())));
+ return createJobId(createJobDataNodeLine());
+ }
+
+ private String createJobId(final JobDataNodeLine jobDataNodeLine) {
+ return PipelineJobIdUtils.marshal(new MigrationJobId(new
PipelineContextKey(InstanceType.PROXY),
Collections.singletonList(jobDataNodeLine.marshal())));
}
private JobDataNodeLine createJobDataNodeLine() {
diff --git
a/kernel/data-pipeline/scenario/migration/distsql/handler/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/handler/update/MigrateTableExecutor.java
b/kernel/data-pipeline/scenario/migration/distsql/handler/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/handler/update/MigrateTableExecutor.java
index c78dd42c3a3..4ff7dfc3ee6 100644
---
a/kernel/data-pipeline/scenario/migration/distsql/handler/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/handler/update/MigrateTableExecutor.java
+++
b/kernel/data-pipeline/scenario/migration/distsql/handler/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/handler/update/MigrateTableExecutor.java
@@ -42,6 +42,7 @@ import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import
org.apache.shardingsphere.infra.yaml.config.swapper.resource.YamlDataSourceConfigurationSwapper;
import org.apache.shardingsphere.mode.manager.ContextManager;
+import
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
import java.util.Collection;
import java.util.LinkedList;
@@ -70,26 +71,33 @@ public final class MigrateTableExecutor implements
DistSQLUpdateExecutor<Migrate
private Collection<MigrationSourceTargetEntry>
getMigrationSourceTargetEntries(final PipelineContextKey contextKey, final
MigrateTableStatement sqlStatement) {
Collection<MigrationSourceTargetEntry> result = new LinkedList<>();
for (MigrationSourceTargetSegment each :
sqlStatement.getSourceTargetEntries()) {
- String schemaName = getSchemaName(contextKey,
each.getSourceDatabaseName(), each.getSourceSchemaName()).orElse(null);
- result.add(new MigrationSourceTargetEntry(new
DataNode(each.getSourceDatabaseName(), schemaName, each.getSourceTableName()),
each.getTargetTableName()));
+ result.add(new
MigrationSourceTargetEntry(getSourceDataNode(contextKey, each),
each.getTargetTableName()));
}
return result;
}
- private Optional<String> getSchemaName(final PipelineContextKey
contextKey, final String sourceDatabaseName, final String sourceSchemaName) {
+ private DataNode getSourceDataNode(final PipelineContextKey contextKey,
final MigrationSourceTargetSegment segment) {
Map<String, DataSourcePoolProperties> metaDataDataSource = new
PipelineDataSourcePersistService().load(contextKey, "MIGRATION");
- DataSourcePoolProperties sourceDataSourcePoolProps =
metaDataDataSource.get(sourceDatabaseName);
+ DataSourcePoolProperties sourceDataSourcePoolProps =
metaDataDataSource.get(segment.getSourceDatabaseName());
if (null == sourceDataSourcePoolProps) {
- return Optional.ofNullable(sourceSchemaName);
+ return new DataNode(segment.getSourceDatabaseName(),
segment.getSourceSchemaName(), segment.getSourceTableName());
}
Map<String, Object> sourceDataSourceProps = new
YamlDataSourceConfigurationSwapper().swapToMap(sourceDataSourcePoolProps);
StandardPipelineDataSourceConfiguration sourceDataSourceConfig = new
StandardPipelineDataSourceConfiguration(sourceDataSourceProps);
if (!new
DatabaseTypeRegistry(sourceDataSourceConfig.getDatabaseType()).getDialectDatabaseMetaData().getSchemaOption().isSchemaAvailable())
{
- return Optional.ofNullable(sourceSchemaName);
+ return new DataNode(segment.getSourceDatabaseName(),
segment.getSourceSchemaName(), segment.getSourceTableName());
}
- return Optional.ofNullable(null == sourceSchemaName
+ String schemaName = getSchemaName(sourceDataSourceConfig,
segment.getSourceSchemaIdentifier()).orElse(null);
+ String tableName =
IdentifierNormalizeEngine.normalize(IdentifierNormalizeEngine.resolvePolicy(sourceDataSourceConfig.getDatabaseType(),
null, IdentifierScope.TABLE),
+
segment.getSourceTableIdentifier().getValueWithQuoteCharacters());
+ return new DataNode(segment.getSourceDatabaseName(), schemaName,
tableName);
+ }
+
+ private Optional<String> getSchemaName(final
StandardPipelineDataSourceConfiguration sourceDataSourceConfig, final
IdentifierValue sourceSchemaIdentifier) {
+ return Optional.ofNullable(null == sourceSchemaIdentifier
? PipelineSchemaUtils.getDefaultSchema(sourceDataSourceConfig)
- :
IdentifierNormalizeEngine.normalize(IdentifierNormalizeEngine.resolvePolicy(sourceDataSourceConfig.getDatabaseType(),
null, IdentifierScope.SCHEMA), sourceSchemaName));
+ :
IdentifierNormalizeEngine.normalize(IdentifierNormalizeEngine.resolvePolicy(sourceDataSourceConfig.getDatabaseType(),
null, IdentifierScope.SCHEMA),
+ sourceSchemaIdentifier.getValueWithQuoteCharacters()));
}
@Override
diff --git
a/kernel/data-pipeline/scenario/migration/distsql/handler/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/handler/update/MigrateTableExecutorTest.java
b/kernel/data-pipeline/scenario/migration/distsql/handler/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/handler/update/MigrateTableExecutorTest.java
index 6f4184fea51..fac1bab0117 100644
---
a/kernel/data-pipeline/scenario/migration/distsql/handler/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/handler/update/MigrateTableExecutorTest.java
+++
b/kernel/data-pipeline/scenario/migration/distsql/handler/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/handler/update/MigrateTableExecutorTest.java
@@ -27,6 +27,7 @@ import
org.apache.shardingsphere.data.pipeline.scenario.migration.api.MigrationS
import
org.apache.shardingsphere.data.pipeline.scenario.migration.distsql.segment.MigrationSourceTargetSegment;
import
org.apache.shardingsphere.data.pipeline.scenario.migration.distsql.statement.updatable.MigrateTableStatement;
import
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import
org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
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.IdentifierNormalizeEngine;
@@ -38,6 +39,7 @@ import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import
org.apache.shardingsphere.infra.yaml.config.swapper.resource.YamlDataSourceConfigurationSwapper;
import org.apache.shardingsphere.mode.manager.ContextManager;
+import
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -71,7 +73,7 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class MigrateTableExecutorTest {
- private static final String SOURCE_DATABASE_NAME = "foo_source";
+ private static final String SOURCE_DATABASE_NAME = "FOO_SOURCE";
private static final String TARGET_DATABASE_NAME = "foo_target";
@@ -140,16 +142,20 @@ class MigrateTableExecutorTest {
schemaUtils.when(() ->
PipelineSchemaUtils.getDefaultSchema(any(StandardPipelineDataSourceConfiguration.class))).thenReturn("public");
executor.executeUpdate(createStatement(null), contextManager);
verify(jobAPI).schedule(any(PipelineContextKey.class),
entriesCaptor.capture(), eq(TARGET_DATABASE_NAME));
-
assertNull(entriesCaptor.getValue().iterator().next().getSource().getSchemaName());
+ MigrationSourceTargetEntry actual =
entriesCaptor.getValue().iterator().next();
+ assertNull(actual.getSource().getSchemaName());
+ assertThat(actual.getSource().getTableName(), is("FOO_TBL"));
schemaUtils.verify(() ->
PipelineSchemaUtils.getDefaultSchema(any(StandardPipelineDataSourceConfiguration.class)),
never());
verify(database, never()).getProtocolType();
}
}
@ParameterizedTest(name = "{0}")
- @MethodSource("provideExplicitSchemaNames")
- void assertExecuteUpdateCanonicalizesExplicitSchema(final String name,
final String sourceSchemaName, final String expectedSchemaName) {
- IdentifierCasePolicy identifierCasePolicy =
IdentifierCasePolicyFactory.newLowerCasePolicySet().getPolicy(IdentifierScope.SCHEMA);
+ @MethodSource("provideExplicitSourceIdentifiers")
+ void assertExecuteUpdateCanonicalizesExplicitSourceIdentifiers(final
String name, final String sourceSchemaName, final String expectedSchemaName,
+ final
IdentifierValue sourceTableIdentifier, final String expectedTableName) {
+ IdentifierCasePolicy schemaIdentifierCasePolicy =
IdentifierCasePolicyFactory.newLowerCasePolicySet().getPolicy(IdentifierScope.SCHEMA);
+ IdentifierCasePolicy tableIdentifierCasePolicy =
IdentifierCasePolicyFactory.newLowerCasePolicySet().getPolicy(IdentifierScope.TABLE);
try (
MockedConstruction<PipelineDataSourcePersistService> ignored =
mockConstruction(PipelineDataSourcePersistService.class,
(mock, context) ->
when(mock.load(any(PipelineContextKey.class),
eq("MIGRATION"))).thenReturn(Collections.singletonMap(SOURCE_DATABASE_NAME,
sourceProps)));
@@ -162,20 +168,26 @@ class MigrateTableExecutorTest {
MockedStatic<PipelineSchemaUtils> schemaUtils =
mockStatic(PipelineSchemaUtils.class);
MockedStatic<IdentifierNormalizeEngine> normalizeEngine =
mockStatic(IdentifierNormalizeEngine.class, Answers.CALLS_REAL_METHODS)) {
spiLoader.when(() ->
TypedSPILoader.getService(TransmissionJobAPI.class,
"MIGRATION")).thenReturn(jobAPI);
- normalizeEngine.when(() ->
IdentifierNormalizeEngine.resolvePolicy(schemaCapableDatabaseType, null,
IdentifierScope.SCHEMA)).thenReturn(identifierCasePolicy);
- executor.executeUpdate(createStatement(sourceSchemaName),
contextManager);
+ normalizeEngine.when(() ->
IdentifierNormalizeEngine.resolvePolicy(schemaCapableDatabaseType, null,
IdentifierScope.SCHEMA)).thenReturn(schemaIdentifierCasePolicy);
+ normalizeEngine.when(() ->
IdentifierNormalizeEngine.resolvePolicy(schemaCapableDatabaseType, null,
IdentifierScope.TABLE)).thenReturn(tableIdentifierCasePolicy);
+ executor.executeUpdate(createStatement(sourceSchemaName,
sourceTableIdentifier), contextManager);
verify(jobAPI).schedule(any(PipelineContextKey.class),
entriesCaptor.capture(), eq(TARGET_DATABASE_NAME));
-
assertThat(entriesCaptor.getValue().iterator().next().getSource().getSchemaName(),
is(expectedSchemaName));
+ MigrationSourceTargetEntry actual =
entriesCaptor.getValue().iterator().next();
+ assertThat(actual.getSource().getDataSourceName(),
is(SOURCE_DATABASE_NAME));
+ assertThat(actual.getSource().getSchemaName(),
is(expectedSchemaName));
+ assertThat(actual.getSource().getTableName(),
is(expectedTableName));
+ assertThat(actual.getTargetTableName(), is("foo_tbl"));
schemaUtils.verify(() ->
PipelineSchemaUtils.getDefaultSchema(any(StandardPipelineDataSourceConfiguration.class)),
never());
}
}
- private static Stream<Arguments> provideExplicitSchemaNames() {
+ private static Stream<Arguments> provideExplicitSourceIdentifiers() {
return Stream.of(
- Arguments.of("lower-case schema", "foo_schema", "foo_schema"),
- Arguments.of("unquoted upper-case schema", "FOO_SCHEMA",
"foo_schema"),
- Arguments.of("quoted upper-case schema", "\"FOO_SCHEMA\"",
"FOO_SCHEMA"),
- Arguments.of("quoted mixed-case schema", "\"CaseSchema\"",
"CaseSchema"));
+ Arguments.of("lower-case schema", "foo_schema", "foo_schema",
new IdentifierValue("FOO_TBL", QuoteCharacter.BACK_QUOTE), "FOO_TBL"),
+ Arguments.of("unquoted upper-case schema", "FOO_SCHEMA",
"foo_schema", new IdentifierValue("FOO_TBL", QuoteCharacter.BACK_QUOTE),
"FOO_TBL"),
+ Arguments.of("quoted upper-case schema", "\"FOO_SCHEMA\"",
"FOO_SCHEMA", new IdentifierValue("FOO_TBL", QuoteCharacter.BACK_QUOTE),
"FOO_TBL"),
+ Arguments.of("quoted mixed-case schema", "\"CaseSchema\"",
"CaseSchema", new IdentifierValue("FOO_TBL", QuoteCharacter.BACK_QUOTE),
"FOO_TBL"),
+ Arguments.of("unquoted upper-case table", "foo_schema",
"foo_schema", new IdentifierValue("FOO_TBL", QuoteCharacter.NONE), "foo_tbl"));
}
@Test
@@ -195,7 +207,13 @@ class MigrateTableExecutorTest {
}
private MigrateTableStatement createStatement(final String
sourceSchemaName) {
- MigrationSourceTargetSegment entry = new
MigrationSourceTargetSegment(SOURCE_DATABASE_NAME, sourceSchemaName, "foo_tbl",
"foo_tbl");
+ return createStatement(sourceSchemaName, new
IdentifierValue("FOO_TBL", QuoteCharacter.BACK_QUOTE));
+ }
+
+ private MigrateTableStatement createStatement(final String
sourceSchemaName, final IdentifierValue sourceTableIdentifier) {
+ MigrationSourceTargetSegment entry = new
MigrationSourceTargetSegment(new IdentifierValue(SOURCE_DATABASE_NAME,
QuoteCharacter.BACK_QUOTE),
+ null == sourceSchemaName ? null : new
IdentifierValue(sourceSchemaName), sourceTableIdentifier,
+ new IdentifierValue("foo_tbl", QuoteCharacter.NONE));
return new MigrateTableStatement(null, Collections.singleton(entry));
}
diff --git
a/kernel/data-pipeline/scenario/migration/distsql/parser/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/parser/core/MigrationDistSQLStatementVisitor.java
b/kernel/data-pipeline/scenario/migration/distsql/parser/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/parser/core/MigrationDistSQLStatementVisitor.java
index ae2f0e12b4b..d93db27b26c 100644
---
a/kernel/data-pipeline/scenario/migration/distsql/parser/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/parser/core/MigrationDistSQLStatementVisitor.java
+++
b/kernel/data-pipeline/scenario/migration/distsql/parser/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/parser/core/MigrationDistSQLStatementVisitor.java
@@ -17,7 +17,6 @@
package
org.apache.shardingsphere.data.pipeline.scenario.migration.distsql.parser.core;
-import com.google.common.base.Splitter;
import org.antlr.v4.runtime.tree.ParseTree;
import
org.apache.shardingsphere.data.pipeline.distsql.statement.updatable.AlterTransmissionRuleStatement;
import
org.apache.shardingsphere.data.pipeline.scenario.migration.distsql.segment.MigrationSourceTargetSegment;
@@ -87,7 +86,6 @@ import
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.Iden
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
-import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
@@ -165,18 +163,15 @@ public final class MigrationDistSQLStatementVisitor
extends MigrationDistSQLStat
}
private String getTargetDatabaseName(final TargetTableNameContext
targetContext) {
- List<String> targetDatabaseNames =
Splitter.on('.').splitToList(getRequiredIdentifierValue(targetContext));
- return targetDatabaseNames.size() > 1 ? targetDatabaseNames.get(0) :
null;
+ return null == targetContext.owner() ? null :
getRequiredIdentifierValue(targetContext.owner());
}
private MigrationSourceTargetSegment getMigrationSourceTargetSegment(final
SourceTableNameContext sourceContext, final TargetTableNameContext
targetContext) {
- List<String> source =
Splitter.on('.').splitToList(getRequiredIdentifierValue(sourceContext));
- List<String> target =
Splitter.on('.').splitToList(getRequiredIdentifierValue(targetContext));
- String sourceDatabaseName = source.get(0);
- String sourceSchemaName = 3 == source.size() ? source.get(1) : null;
- String sourceTableName = source.get(source.size() - 1);
- String targetTableName = target.get(target.size() - 1);
- return new MigrationSourceTargetSegment(sourceDatabaseName,
sourceSchemaName, sourceTableName, targetTableName);
+ IdentifierValue sourceDatabaseIdentifier = new
IdentifierValue(sourceContext.owner().getText());
+ IdentifierValue sourceSchemaIdentifier = null ==
sourceContext.schema() ? null : new
IdentifierValue(sourceContext.schema().getText());
+ IdentifierValue sourceTableIdentifier = new
IdentifierValue(sourceContext.name().getText());
+ IdentifierValue targetTableIdentifier = new
IdentifierValue(targetContext.name().getText());
+ return new MigrationSourceTargetSegment(sourceDatabaseIdentifier,
sourceSchemaIdentifier, sourceTableIdentifier, targetTableIdentifier);
}
private String getRequiredIdentifierValue(final ParseTree context) {
diff --git
a/kernel/data-pipeline/scenario/migration/distsql/parser/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/parser/core/MigrationDistSQLStatementVisitorTest.java
b/kernel/data-pipeline/scenario/migration/distsql/parser/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/parser/core/MigrationDistSQLStatementVisitorTest.java
new file mode 100644
index 00000000000..d729fc44865
--- /dev/null
+++
b/kernel/data-pipeline/scenario/migration/distsql/parser/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/parser/core/MigrationDistSQLStatementVisitorTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package
org.apache.shardingsphere.data.pipeline.scenario.migration.distsql.parser.core;
+
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import
org.apache.shardingsphere.data.pipeline.scenario.migration.distsql.segment.MigrationSourceTargetSegment;
+import
org.apache.shardingsphere.data.pipeline.scenario.migration.distsql.statement.updatable.MigrateTableStatement;
+import
org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
+import org.apache.shardingsphere.sql.parser.engine.core.ParseASTNode;
+import
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class MigrationDistSQLStatementVisitorTest {
+
+ @Test
+ void assertVisitMigrateTablePreservesQualifiedIdentifiers() {
+ MigrationDistSQLLexer lexer = new
MigrationDistSQLLexer(CharStreams.fromString(
+ "MIGRATE TABLE `FOO_DS`.`FOO_SCHEMA`.`FOO_TABLE` INTO
target_db.FOO_TABLE"));
+ MigrationDistSQLParser parser = new MigrationDistSQLParser(new
CommonTokenStream(lexer));
+ ParseASTNode parseASTNode = (ParseASTNode) parser.parse();
+ MigrateTableStatement statement = (MigrateTableStatement) new
MigrationDistSQLStatementVisitor().visit(parseASTNode.getRootNode());
+ MigrationSourceTargetSegment actual =
statement.getSourceTargetEntries().iterator().next();
+ assertThat(actual.getSourceDatabaseIdentifier(), is(new
IdentifierValue("FOO_DS", QuoteCharacter.BACK_QUOTE)));
+ assertThat(actual.getSourceSchemaIdentifier(), is(new
IdentifierValue("FOO_SCHEMA", QuoteCharacter.BACK_QUOTE)));
+ assertThat(actual.getSourceTableIdentifier(), is(new
IdentifierValue("FOO_TABLE", QuoteCharacter.BACK_QUOTE)));
+ assertThat(actual.getTargetTableIdentifier(), is(new
IdentifierValue("FOO_TABLE", QuoteCharacter.NONE)));
+ assertThat(statement.getTargetDatabaseName(), is("target_db"));
+ }
+}
diff --git
a/kernel/data-pipeline/scenario/migration/distsql/statement/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/segment/MigrationSourceTargetSegment.java
b/kernel/data-pipeline/scenario/migration/distsql/statement/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/segment/MigrationSourceTargetSegment.java
index 91fd3cb0ad1..6015f90914b 100644
---
a/kernel/data-pipeline/scenario/migration/distsql/statement/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/segment/MigrationSourceTargetSegment.java
+++
b/kernel/data-pipeline/scenario/migration/distsql/statement/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/segment/MigrationSourceTargetSegment.java
@@ -20,6 +20,7 @@ package
org.apache.shardingsphere.data.pipeline.scenario.migration.distsql.segme
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.shardingsphere.distsql.segment.DistSQLSegment;
+import
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
/**
* Migration source target segment.
@@ -28,11 +29,60 @@ import
org.apache.shardingsphere.distsql.segment.DistSQLSegment;
@Getter
public final class MigrationSourceTargetSegment implements DistSQLSegment {
- private final String sourceDatabaseName;
+ private final IdentifierValue sourceDatabaseIdentifier;
- private final String sourceSchemaName;
+ private final IdentifierValue sourceSchemaIdentifier;
- private final String sourceTableName;
+ private final IdentifierValue sourceTableIdentifier;
- private final String targetTableName;
+ private final IdentifierValue targetTableIdentifier;
+
+ /**
+ * Construct a migration source target segment from textual identifiers.
+ *
+ * @param sourceDatabaseName source database name
+ * @param sourceSchemaName source schema name
+ * @param sourceTableName source table name
+ * @param targetTableName target table name
+ */
+ public MigrationSourceTargetSegment(final String sourceDatabaseName, final
String sourceSchemaName, final String sourceTableName, final String
targetTableName) {
+ this(new IdentifierValue(sourceDatabaseName), null == sourceSchemaName
? null : new IdentifierValue(sourceSchemaName),
+ new IdentifierValue(sourceTableName), new
IdentifierValue(targetTableName));
+ }
+
+ /**
+ * Get source database name.
+ *
+ * @return source database name
+ */
+ public String getSourceDatabaseName() {
+ return sourceDatabaseIdentifier.getValue();
+ }
+
+ /**
+ * Get source schema name.
+ *
+ * @return source schema name
+ */
+ public String getSourceSchemaName() {
+ return null == sourceSchemaIdentifier ? null :
sourceSchemaIdentifier.getValue();
+ }
+
+ /**
+ * Get source table name.
+ *
+ * @return source table name
+ */
+ public String getSourceTableName() {
+ return sourceTableIdentifier.getValue();
+ }
+
+ /**
+ * Get target table name.
+ *
+ * @return target table name
+ */
+ public String getTargetTableName() {
+ return targetTableIdentifier.getValue();
+ }
}
diff --git
a/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/cases/migration/general/MySQLMigrationIdentifierCaseE2EIT.java
b/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/cases/migration/general/MySQLMigrationIdentifierCaseE2EIT.java
new file mode 100644
index 00000000000..c4dc39e327f
--- /dev/null
+++
b/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/cases/migration/general/MySQLMigrationIdentifierCaseE2EIT.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package
org.apache.shardingsphere.test.e2e.operation.pipeline.cases.migration.general;
+
+import com.google.common.collect.ImmutableMap;
+import
org.apache.shardingsphere.data.pipeline.scenario.migration.MigrationJobType;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.cases.PipelineContainerComposer;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.cases.migration.AbstractMigrationE2EIT;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.param.PipelineE2ECondition;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.param.PipelineE2ESettings;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.param.PipelineE2ESettings.PipelineE2EDatabaseSettings;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.param.PipelineE2ETestCaseArgumentsProvider;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.param.PipelineTestParameter;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.util.PipelineE2EDistSQLFacade;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.condition.EnabledIf;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.concurrent.TimeUnit;
+
+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;
+
+@PipelineE2ESettings(database = @PipelineE2EDatabaseSettings(type = "MySQL"))
+class MySQLMigrationIdentifierCaseE2EIT extends AbstractMigrationE2EIT {
+
+ private static final String SOURCE_TABLE_NAME = "t_order_copy";
+
+ private static final String TARGET_TABLE_NAME = "T_ORDER";
+
+ private static final long INVENTORY_ORDER_ID = 1L;
+
+ private static final long INCREMENTAL_ORDER_ID = 2L;
+
+ @ParameterizedTest(name = "{0}")
+ @EnabledIf("isEnabled")
+ @ArgumentsSource(PipelineE2ETestCaseArgumentsProvider.class)
+ void assertMigrationToUpperCaseTargetTable(final PipelineTestParameter
testParam) throws SQLException {
+ try (PipelineContainerComposer containerComposer = new
PipelineContainerComposer(testParam)) {
+ assertCaseSensitiveTableNames(containerComposer);
+ PipelineE2EDistSQLFacade distSQLFacade = new
PipelineE2EDistSQLFacade(containerComposer, new MigrationJobType());
+ distSQLFacade.alterPipelineRule();
+ containerComposer.sourceExecuteWithLog(String.format(
+ "CREATE TABLE %s (order_id BIGINT PRIMARY KEY, user_id INT
NOT NULL, status VARCHAR(32) NOT NULL)", SOURCE_TABLE_NAME));
+ addMigrationSourceResource(containerComposer);
+ addMigrationTargetResource(containerComposer);
+ createTargetTableRule(containerComposer);
+ insertSourceRecord(containerComposer, INVENTORY_ORDER_ID);
+ containerComposer.proxyExecuteWithLog(String.format("MIGRATE TABLE
ds_0.%s INTO sharding_DB.%s", SOURCE_TABLE_NAME, TARGET_TABLE_NAME), 5);
+ Awaitility.waitAtMost(10L, TimeUnit.SECONDS).pollInterval(1L,
TimeUnit.SECONDS).until(() -> !distSQLFacade.listJobIds().isEmpty());
+ String jobId = distSQLFacade.getJobIdByTableName("ds_0." +
SOURCE_TABLE_NAME);
+ distSQLFacade.waitJobPreparingStageFinished(jobId);
+ distSQLFacade.waitJobIncrementalStageStarted(jobId);
+ insertSourceRecord(containerComposer, INCREMENTAL_ORDER_ID);
+
assertFalse(distSQLFacade.waitJobIncrementalStageFinished(jobId).isEmpty(),
"Migration incremental stage did not become idle");
+ DataSource targetDataSource =
containerComposer.generateShardingSphereDataSourceFromProxy();
+ containerComposer.assertRecordExists(targetDataSource,
TARGET_TABLE_NAME, INVENTORY_ORDER_ID);
+ containerComposer.assertRecordExists(targetDataSource,
TARGET_TABLE_NAME, INCREMENTAL_ORDER_ID);
+ distSQLFacade.startCheck(jobId, "DATA_MATCH",
ImmutableMap.of("chunk-size", "300", "streaming-range-type", "SMALL"));
+ distSQLFacade.verifyCheck(jobId);
+ distSQLFacade.commit(jobId);
+ assertTrue(distSQLFacade.listJobIds().isEmpty());
+ }
+ }
+
+ private void assertCaseSensitiveTableNames(final PipelineContainerComposer
containerComposer) throws SQLException {
+ try (
+ Connection connection =
containerComposer.getSourceDataSource().getConnection();
+ Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery("SELECT
@@lower_case_table_names")) {
+ assertTrue(resultSet.next());
+ assertThat(resultSet.getInt(1), is(0));
+ }
+ }
+
+ private void createTargetTableRule(final PipelineContainerComposer
containerComposer) throws SQLException {
+ containerComposer.proxyExecuteWithLog(String.format("CREATE SHARDING
TABLE RULE %s(STORAGE_UNITS(ds_2,ds_3,ds_4),SHARDING_COLUMN=user_id,"
+ + "TYPE(NAME='hash_mod',PROPERTIES('sharding-count'='6')))",
TARGET_TABLE_NAME), 0);
+ Awaitility.waitAtMost(10L, TimeUnit.SECONDS).pollInterval(1L,
TimeUnit.SECONDS)
+ .until(() -> !containerComposer.queryForListWithLog("SHOW
SHARDING TABLE RULE " + TARGET_TABLE_NAME).isEmpty());
+ }
+
+ private void insertSourceRecord(final PipelineContainerComposer
containerComposer, final long orderId) throws SQLException {
+ containerComposer.sourceExecuteWithLog(String.format("INSERT INTO %s
VALUES (%d, 1, 'OK')", SOURCE_TABLE_NAME, orderId));
+ }
+
+ private static boolean isEnabled(final ExtensionContext context) {
+ return PipelineE2ECondition.isEnabled(context);
+ }
+}
diff --git
a/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/cases/migration/general/PostgreSQLMigrationIdentifierCaseE2EIT.java
b/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/cases/migration/general/PostgreSQLMigrationIdentifierCaseE2EIT.java
new file mode 100644
index 00000000000..025715ebcea
--- /dev/null
+++
b/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/cases/migration/general/PostgreSQLMigrationIdentifierCaseE2EIT.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package
org.apache.shardingsphere.test.e2e.operation.pipeline.cases.migration.general;
+
+import lombok.extern.slf4j.Slf4j;
+import
org.apache.shardingsphere.data.pipeline.scenario.migration.MigrationJobType;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.cases.PipelineContainerComposer;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.cases.migration.AbstractMigrationE2EIT;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.param.PipelineE2ECondition;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.param.PipelineE2ESettings;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.param.PipelineE2ESettings.PipelineE2EDatabaseSettings;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.param.PipelineE2ETestCaseArgumentsProvider;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.param.PipelineTestParameter;
+import
org.apache.shardingsphere.test.e2e.operation.pipeline.util.PipelineE2EDistSQLFacade;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.condition.EnabledIf;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.time.LocalDateTime;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Slf4j
+@PipelineE2ESettings(database = @PipelineE2EDatabaseSettings(type =
"PostgreSQL"))
+class PostgreSQLMigrationIdentifierCaseE2EIT extends AbstractMigrationE2EIT {
+
+ private static final String SOURCE_SCHEMA_NAME = "UPPER_SCHEMA";
+
+ private static final String SOURCE_TABLE_NAME = "UPPER_TABLE";
+
+ private static final String TARGET_TABLE_NAME = "upper_schema.t_order";
+
+ private static final long INVENTORY_ORDER_ID = 1L;
+
+ private static final long INCREMENTAL_ORDER_ID = 2L;
+
+ private static final long LOWER_CASE_CONFLICT_ORDER_ID = 20001L;
+
+ private static final long CROSS_SCHEMA_CONFLICT_ORDER_ID = 20002L;
+
+ @ParameterizedTest(name = "{0}")
+ @EnabledIf("isEnabled")
+ @ArgumentsSource(PipelineE2ETestCaseArgumentsProvider.class)
+ void assertMigrationWithUpperCaseSourceIdentifiers(final
PipelineTestParameter testParam) throws SQLException {
+ try (PipelineContainerComposer containerComposer = new
PipelineContainerComposer(testParam)) {
+ PipelineE2EDistSQLFacade distSQLFacade = new
PipelineE2EDistSQLFacade(containerComposer, new MigrationJobType());
+ distSQLFacade.alterPipelineRule();
+ createSourceTables(containerComposer);
+ addMigrationSourceResource(containerComposer);
+ addMigrationTargetResource(containerComposer);
+ createTargetOrderTableRule(containerComposer);
+ insertSourceRecord(containerComposer, qualifiedUpperCaseTable(),
INVENTORY_ORDER_ID);
+ int replicationSlotsCount =
getReplicationSlotsCount(containerComposer);
+ log.info("Start migration at {}, replication slots count: {}",
LocalDateTime.now(), replicationSlotsCount);
+ containerComposer.proxyExecuteWithLog("MIGRATE TABLE
ds_0.`UPPER_SCHEMA`.`UPPER_TABLE` INTO sharding_DB.T_ORDER", 5);
+ Awaitility.waitAtMost(10L, TimeUnit.SECONDS).pollInterval(1L,
TimeUnit.SECONDS).until(() -> !distSQLFacade.listJobIds().isEmpty());
+ String jobId =
distSQLFacade.getJobIdByTableName("ds_0.UPPER_SCHEMA.UPPER_TABLE");
+ distSQLFacade.waitJobPreparingStageFinished(jobId);
+ distSQLFacade.waitJobIncrementalStageStarted(jobId);
+ insertSourceRecord(containerComposer, qualifiedUpperCaseTable(),
INCREMENTAL_ORDER_ID);
+ insertSourceRecord(containerComposer, "upper_schema.upper_table",
LOWER_CASE_CONFLICT_ORDER_ID);
+ insertSourceRecord(containerComposer, "public.\"UPPER_TABLE\"",
CROSS_SCHEMA_CONFLICT_ORDER_ID);
+
assertFalse(distSQLFacade.waitJobIncrementalStageFinished(jobId).isEmpty(),
"Migration incremental stage did not become idle");
+ DataSource targetDataSource =
containerComposer.generateShardingSphereDataSourceFromProxy();
+ containerComposer.assertRecordExists(targetDataSource,
TARGET_TABLE_NAME, INVENTORY_ORDER_ID);
+ containerComposer.assertRecordExists(targetDataSource,
TARGET_TABLE_NAME, INCREMENTAL_ORDER_ID);
+ assertRecordAbsent(targetDataSource, LOWER_CASE_CONFLICT_ORDER_ID);
+ assertRecordAbsent(targetDataSource,
CROSS_SCHEMA_CONFLICT_ORDER_ID);
+ distSQLFacade.startCheckAndVerify(jobId, "DATA_MATCH");
+ distSQLFacade.commit(jobId);
+ assertTrue(distSQLFacade.listJobIds().isEmpty());
+ Awaitility.waitAtMost(30L, TimeUnit.SECONDS).pollInterval(1L,
TimeUnit.SECONDS)
+ .until(() -> getReplicationSlotsCount(containerComposer)
== replicationSlotsCount);
+ }
+ }
+
+ private void createSourceTables(final PipelineContainerComposer
containerComposer) throws SQLException {
+ containerComposer.sourceExecuteWithLog("CREATE SCHEMA IF NOT EXISTS
\"UPPER_SCHEMA\"");
+ containerComposer.sourceExecuteWithLog("CREATE SCHEMA IF NOT EXISTS
upper_schema");
+
containerComposer.sourceExecuteWithLog(createTableSQL(qualifiedUpperCaseTable(),
"\"ORDER_ID\"", "\"USER_ID\"", "\"STATUS\""));
+
containerComposer.sourceExecuteWithLog(createTableSQL("upper_schema.upper_table",
"order_id", "user_id", "status"));
+
containerComposer.sourceExecuteWithLog(createTableSQL("public.\"UPPER_TABLE\"",
"\"ORDER_ID\"", "\"USER_ID\"", "\"STATUS\""));
+ }
+
+ private String createTableSQL(final String tableName, final String
orderIdColumn, final String userIdColumn, final String statusColumn) {
+ return String.format("CREATE TABLE %s (%s BIGINT PRIMARY KEY, %s INT
NOT NULL, %s VARCHAR(32) NOT NULL)", tableName, orderIdColumn, userIdColumn,
statusColumn);
+ }
+
+ private void insertSourceRecord(final PipelineContainerComposer
containerComposer, final String tableName, final long orderId) throws
SQLException {
+ containerComposer.sourceExecuteWithLog(String.format("INSERT INTO %s
VALUES (%d, 1, 'OK')", tableName, orderId));
+ }
+
+ private void assertRecordAbsent(final DataSource dataSource, final long
orderId) throws SQLException {
+ try (
+ Connection connection = dataSource.getConnection();
+ Statement statement = connection.createStatement();
+ ResultSet resultSet =
statement.executeQuery(String.format("SELECT 1 FROM %s WHERE order_id = %d",
TARGET_TABLE_NAME, orderId))) {
+ assertFalse(resultSet.next(), "A record from a conflicting source
table must not be migrated");
+ }
+ }
+
+ private int getReplicationSlotsCount(final PipelineContainerComposer
containerComposer) throws SQLException {
+ try (
+ Connection connection =
containerComposer.getSourceDataSource().getConnection();
+ Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery("SELECT COUNT(1)
FROM pg_replication_slots")) {
+ return resultSet.next() ? resultSet.getInt(1) : 0;
+ }
+ }
+
+ private String qualifiedUpperCaseTable() {
+ return String.format("\"%s\".\"%s\"", SOURCE_SCHEMA_NAME,
SOURCE_TABLE_NAME);
+ }
+
+ private static boolean isEnabled(final ExtensionContext context) {
+ return PipelineE2ECondition.isEnabled(context);
+ }
+}
diff --git
a/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/framework/container/compose/natived/PipelineNativeContainerComposer.java
b/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/framework/container/compose/natived/PipelineNativeContainerComposer.java
index 570ff171322..24d8aeb048b 100644
---
a/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/framework/container/compose/natived/PipelineNativeContainerComposer.java
+++
b/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/framework/container/compose/natived/PipelineNativeContainerComposer.java
@@ -18,8 +18,10 @@
package
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.container.compose.natived;
import lombok.SneakyThrows;
+import
org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
import
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
import
org.apache.shardingsphere.test.e2e.env.container.storage.option.StorageContainerOption;
import org.apache.shardingsphere.test.e2e.env.runtime.E2ETestEnvironment;
import
org.apache.shardingsphere.test.e2e.operation.pipeline.framework.container.compose.PipelineBaseContainerComposer;
@@ -67,10 +69,11 @@ public final class PipelineNativeContainerComposer extends
PipelineBaseContainer
private void dropTable(final Connection connection, final String
databaseName) throws SQLException {
Map<String, List<String>> schemaAndTableMapper =
getSchemaAndTableMapper(connection, databaseName);
+ QuoteCharacter quoteCharacter = new
DatabaseTypeRegistry(databaseType).getDialectDatabaseMetaData().getQuoteCharacter();
try (Statement statement = connection.createStatement()) {
for (Entry<String, List<String>> entry :
schemaAndTableMapper.entrySet()) {
for (String each : entry.getValue()) {
- statement.executeUpdate(String.format("DROP TABLE %s.%s",
entry.getKey(), each));
+ statement.executeUpdate(String.format("DROP TABLE %s.%s",
quoteCharacter.wrap(entry.getKey()), quoteCharacter.wrap(each)));
}
}
Optional<String> dropSchemaSQL =
dropTableOption.getDropSchemaSQL();
diff --git
a/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/framework/container/compose/natived/dialect/PostgreSQLPipelineNativeContainerDropTableOption.java
b/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/framework/container/compose/natived/dialect/PostgreSQLPipelineNativeContainerDropTableOption.java
index 5ea3ce0e33a..8be12eec2be 100644
---
a/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/framework/container/compose/natived/dialect/PostgreSQLPipelineNativeContainerDropTableOption.java
+++
b/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/framework/container/compose/natived/dialect/PostgreSQLPipelineNativeContainerDropTableOption.java
@@ -34,7 +34,7 @@ public final class
PostgreSQLPipelineNativeContainerDropTableOption implements D
@Override
public String getQueryAllSchemaAndTableMapperSQL(final String
databaseName) {
- return "SELECT schemaname, tablename FROM pg_tables WHERE
schemaname='public' OR schemaname='test'";
+ return "SELECT schemaname, tablename FROM pg_tables WHERE schemaname
NOT LIKE 'pg_%' AND schemaname <> 'information_schema'";
}
@Override
diff --git
a/test/e2e/operation/pipeline/src/test/resources/env/container/mysql/cnf/5/my.cnf
b/test/e2e/operation/pipeline/src/test/resources/env/container/mysql/cnf/5/my.cnf
index 5e034f5c393..d7a0a560c92 100644
---
a/test/e2e/operation/pipeline/src/test/resources/env/container/mysql/cnf/5/my.cnf
+++
b/test/e2e/operation/pipeline/src/test/resources/env/container/mysql/cnf/5/my.cnf
@@ -24,7 +24,7 @@ log-bin=mysql-bin
binlog-format=row
binlog-row-image=full
max_connections=600
-lower_case_table_names=1
+lower_case_table_names=0
innodb_lock_wait_timeout=5
default-time-zone='+00:00'
default-authentication-plugin=mysql_native_password
diff --git
a/test/e2e/operation/pipeline/src/test/resources/env/container/mysql/cnf/8/my.cnf
b/test/e2e/operation/pipeline/src/test/resources/env/container/mysql/cnf/8/my.cnf
index 7bfabebe18a..4266f8c269c 100644
---
a/test/e2e/operation/pipeline/src/test/resources/env/container/mysql/cnf/8/my.cnf
+++
b/test/e2e/operation/pipeline/src/test/resources/env/container/mysql/cnf/8/my.cnf
@@ -24,7 +24,7 @@ log-bin=mysql-bin
binlog-format=row
binlog-row-image=full
max_connections=600
-lower_case_table_names=1
+lower_case_table_names=0
innodb_lock_wait_timeout=5
default-time-zone='+00:00'
default-authentication-plugin=caching_sha2_password