This is an automated email from the ASF dual-hosted git repository.

terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new f016aa5cd90 Fix Pipeline schema identity handling (#39598)
f016aa5cd90 is described below

commit f016aa5cd908b3de8d001b9a5c7d16246667e9b9
Author: Liang Zhang <[email protected]>
AuthorDate: Tue Aug 25 00:49:01 2026 +0800

    Fix Pipeline schema identity handling (#39598)
    
    - resolve Migration default schemas from source database types
    - reject ambiguous same-name tables before job creation
    - preserve schema-aware CDC data node selection
    - filter PostgreSQL and openGauss WAL events by schema
---
 .../pipeline/core/util/PipelineDataNodeUtils.java  |  36 +++-
 .../core/util/PipelineDataNodeUtilsTest.java       | 124 +++++++++++-
 .../ingest/incremental/wal/WALEventConverter.java  |  81 +++++---
 .../incremental/wal/WALEventConverterTest.java     |  97 ++++++++--
 .../data/pipeline/cdc/api/CDCJobAPI.java           |  13 ++
 .../pipeline/cdc/handler/CDCBackendHandler.java    |  24 +--
 .../pipeline/cdc/util/CDCSchemaTableUtils.java     |  53 +++--
 .../data/pipeline/cdc/api/CDCJobAPITest.java       |  35 +++-
 .../cdc/handler/CDCBackendHandlerTest.java         |  38 ++--
 .../pipeline/cdc/util/CDCSchemaTableUtilsTest.java | 128 +++++++++---
 .../scenario/migration/api/MigrationJobAPI.java    |  18 +-
 .../migration/api/MigrationJobAPITest.java         | 100 ++++++++++
 .../handler/update/MigrateTableExecutor.java       |  23 ++-
 .../handler/update/MigrateTableExecutorTest.java   | 214 +++++++++++++++++++++
 .../general/PostgreSQLMigrationGeneralE2EIT.java   |  17 ++
 15 files changed, 852 insertions(+), 149 deletions(-)

diff --git 
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/util/PipelineDataNodeUtils.java
 
b/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/util/PipelineDataNodeUtils.java
index 81ad3a165f4..22864e704db 100644
--- 
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/util/PipelineDataNodeUtils.java
+++ 
b/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/util/PipelineDataNodeUtils.java
@@ -31,6 +31,9 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Pipeline data node utility class.
@@ -42,30 +45,43 @@ public final class PipelineDataNodeUtils {
      * Build table and data nodes map.
      *
      * @param database database
-     * @param tableNames table names
+     * @param schemaTableNames resolved schema table names, empty schema name 
means schema unavailable
      * @return table and data nodes map
      * @throws PipelineInvalidParameterException thrown invalid parameter 
exception when can't get data nodes.
      */
-    public static Map<String, List<DataNode>> buildTableAndDataNodesMap(final 
ShardingSphereDatabase database, final Collection<String> tableNames) {
-        ShardingSpherePreconditions.checkNotEmpty(tableNames, () -> new 
PipelineInvalidParameterException("Table names are empty."));
-        Map<String, List<DataNode>> result = new HashMap<>(tableNames.size(), 
1F);
+    public static Map<String, List<DataNode>> buildTableAndDataNodesMap(final 
ShardingSphereDatabase database, final Map<String, Set<String>> 
schemaTableNames) {
+        int tableCount = 
schemaTableNames.values().stream().mapToInt(Collection::size).sum();
+        ShardingSpherePreconditions.checkState(0 < tableCount, () -> new 
PipelineInvalidParameterException("Table names are empty."));
+        Map<String, List<DataNode>> result = new HashMap<>(tableCount, 1F);
         Collection<DataNodeRuleAttribute> attributes = 
database.getRuleMetaData().getAttributes(DataNodeRuleAttribute.class);
         // TODO support virtual data source name
-        for (String each : tableNames) {
-            Collection<DataNode> dataNodes = findDataNodes(each, attributes);
-            ShardingSpherePreconditions.checkNotEmpty(dataNodes, () -> new 
PipelineInvalidParameterException(String.format("Not find actual data nodes of 
`%s`", each)));
-            result.put(each, new ArrayList<>(dataNodes));
+        for (Entry<String, Set<String>> entry : schemaTableNames.entrySet()) {
+            for (String each : entry.getValue()) {
+                
ShardingSpherePreconditions.checkState(!result.containsKey(each),
+                        () -> new 
PipelineInvalidParameterException(String.format("More than one schema table has 
the same table name `%s`.", each)));
+                Collection<DataNode> dataNodes = findDataNodes(entry.getKey(), 
each, attributes);
+                ShardingSpherePreconditions.checkNotEmpty(dataNodes, () -> new 
PipelineInvalidParameterException(String.format("Not find actual data nodes of 
`%s`", each)));
+                result.put(each, new ArrayList<>(dataNodes));
+            }
         }
         return result;
     }
     
-    private static Collection<DataNode> findDataNodes(final String tableName, 
final Collection<DataNodeRuleAttribute> attributes) {
+    private static Collection<DataNode> findDataNodes(final String schemaName, 
final String tableName, final Collection<DataNodeRuleAttribute> attributes) {
         for (DataNodeRuleAttribute each : attributes) {
             Collection<DataNode> dataNodes = 
each.getDataNodesByTableName(tableName);
             if (!dataNodes.isEmpty()) {
-                return each.isReplicaBasedDistribution() ? 
Collections.singleton(dataNodes.iterator().next()) : dataNodes;
+                Collection<DataNode> matchedDataNodes = 
filterDataNodesBySchema(schemaName, each, dataNodes);
+                return matchedDataNodes.isEmpty() || 
!each.isReplicaBasedDistribution() ? matchedDataNodes : 
Collections.singleton(matchedDataNodes.iterator().next());
             }
         }
         return Collections.emptyList();
     }
+    
+    private static Collection<DataNode> filterDataNodesBySchema(final String 
schemaName, final DataNodeRuleAttribute ruleAttribute, final 
Collection<DataNode> dataNodes) {
+        if (schemaName.isEmpty() || 
!ruleAttribute.isDataNodeTableNameLoadedFromStorage()) {
+            return dataNodes;
+        }
+        return dataNodes.stream().filter(each -> 
schemaName.equals(each.getSchemaName())).collect(Collectors.toList());
+    }
 }
diff --git 
a/kernel/data-pipeline/core/src/test/java/org/apache/shardingsphere/data/pipeline/core/util/PipelineDataNodeUtilsTest.java
 
b/kernel/data-pipeline/core/src/test/java/org/apache/shardingsphere/data/pipeline/core/util/PipelineDataNodeUtilsTest.java
index e3bd5b012d9..236dced5e0c 100644
--- 
a/kernel/data-pipeline/core/src/test/java/org/apache/shardingsphere/data/pipeline/core/util/PipelineDataNodeUtilsTest.java
+++ 
b/kernel/data-pipeline/core/src/test/java/org/apache/shardingsphere/data/pipeline/core/util/PipelineDataNodeUtilsTest.java
@@ -25,41 +25,145 @@ import 
org.apache.shardingsphere.infra.rule.attribute.datanode.DataNodeRuleAttri
 import org.junit.jupiter.api.Test;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
-import static org.hamcrest.Matchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 class PipelineDataNodeUtilsTest {
     
     @Test
     void assertBuildTableAndDataNodesMap() {
-        RuleMetaData ruleMetaData = mock(RuleMetaData.class);
         DataNodeRuleAttribute notReplicaBasedDistributionDataNodeRuleAttribute 
= mock(DataNodeRuleAttribute.class);
         
when(notReplicaBasedDistributionDataNodeRuleAttribute.getDataNodesByTableName("foo_tbl")).thenReturn(Collections.singleton(new
 DataNode("foo_ds.foo_tbl")));
         DataNodeRuleAttribute replicaBasedDistributionDataNodeRuleAttribute = 
mock(DataNodeRuleAttribute.class);
+        
when(replicaBasedDistributionDataNodeRuleAttribute.getDataNodesByTableName("foo_tbl")).thenReturn(Collections.singleton(new
 DataNode("bar_ds.foo_tbl")));
         
when(replicaBasedDistributionDataNodeRuleAttribute.getDataNodesByTableName("bar_tbl")).thenReturn(Arrays.asList(new
 DataNode("foo_ds.bar_tbl"), new DataNode("bar_ds.bar_tbl")));
+        
when(replicaBasedDistributionDataNodeRuleAttribute.isDataNodeTableNameLoadedFromStorage()).thenReturn(true);
         
when(replicaBasedDistributionDataNodeRuleAttribute.isReplicaBasedDistribution()).thenReturn(true);
-        
when(ruleMetaData.getAttributes(DataNodeRuleAttribute.class)).thenReturn(Arrays.asList(notReplicaBasedDistributionDataNodeRuleAttribute,
 replicaBasedDistributionDataNodeRuleAttribute));
-        ShardingSphereDatabase database = mock(ShardingSphereDatabase.class);
-        when(database.getRuleMetaData()).thenReturn(ruleMetaData);
-        Map<String, List<DataNode>> actual = 
PipelineDataNodeUtils.buildTableAndDataNodesMap(database, 
Arrays.asList("foo_tbl", "bar_tbl"));
+        ShardingSphereDatabase database = 
mockDatabase(Arrays.asList(notReplicaBasedDistributionDataNodeRuleAttribute, 
replicaBasedDistributionDataNodeRuleAttribute));
+        Map<String, Set<String>> schemaTableNames = 
Collections.singletonMap("", new LinkedHashSet<>(Arrays.asList("foo_tbl", 
"bar_tbl")));
+        Map<String, List<DataNode>> actual = 
PipelineDataNodeUtils.buildTableAndDataNodesMap(database, schemaTableNames);
         assertThat(actual.size(), is(2));
         assertThat(actual.get("foo_tbl"), is(Collections.singletonList(new 
DataNode("foo_ds.foo_tbl"))));
         assertThat(actual.get("bar_tbl"), is(Collections.singletonList(new 
DataNode("foo_ds.bar_tbl"))));
+        verify(replicaBasedDistributionDataNodeRuleAttribute, 
never()).getDataNodesByTableName("foo_tbl");
+    }
+    
+    @Test
+    void 
assertBuildTableAndDataNodesMapFiltersStorageLoadedDataNodesBeforeSelectingReplica()
 {
+        DataNode mismatchedDataNode = new DataNode("foo_ds", "caseschema", 
"foo_tbl");
+        DataNode matchedDataNode = new DataNode("bar_ds", "CaseSchema", 
"foo_tbl");
+        DataNodeRuleAttribute ruleAttribute = mockRuleAttribute("foo_tbl", 
Arrays.asList(mismatchedDataNode, matchedDataNode), true, true);
+        ShardingSphereDatabase database = 
mockDatabase(Collections.singleton(ruleAttribute));
+        Map<String, List<DataNode>> actual = 
PipelineDataNodeUtils.buildTableAndDataNodesMap(
+                database, Collections.singletonMap("CaseSchema", 
Collections.singleton("foo_tbl")));
+        assertThat(actual, is(Collections.singletonMap("foo_tbl", 
Collections.singletonList(matchedDataNode))));
+        Map<String, List<DataNode>> lowerCaseActual = 
PipelineDataNodeUtils.buildTableAndDataNodesMap(
+                database, Collections.singletonMap("caseschema", 
Collections.singleton("foo_tbl")));
+        assertThat(lowerCaseActual, is(Collections.singletonMap("foo_tbl", 
Collections.singletonList(mismatchedDataNode))));
+    }
+    
+    @Test
+    void 
assertBuildTableAndDataNodesMapDoesNotFilterOwnerNotLoadedFromStorage() {
+        DataNode dataNode = new DataNode("foo_ds", "bar_schema", "foo_tbl");
+        ShardingSphereDatabase database = 
mockDatabase(Collections.singleton(mockRuleAttribute("foo_tbl", 
Collections.singleton(dataNode), false, false)));
+        Map<String, List<DataNode>> actual = 
PipelineDataNodeUtils.buildTableAndDataNodesMap(
+                database, Collections.singletonMap("foo_schema", 
Collections.singleton("foo_tbl")));
+        assertThat(actual, is(Collections.singletonMap("foo_tbl", 
Collections.singletonList(dataNode))));
+    }
+    
+    @Test
+    void 
assertBuildTableAndDataNodesMapDoesNotFallThroughWhenStorageLoadedOwnerHasNoMatchingSchema()
 {
+        DataNodeRuleAttribute storageLoadedOwner = mockRuleAttribute(
+                "foo_tbl", Collections.singleton(new DataNode("foo_ds", 
"bar_schema", "foo_tbl")), true, false);
+        DataNodeRuleAttribute laterOwner = mock(DataNodeRuleAttribute.class);
+        ShardingSphereDatabase database = 
mockDatabase(Arrays.asList(storageLoadedOwner, laterOwner));
+        PipelineInvalidParameterException actual = 
assertThrows(PipelineInvalidParameterException.class, () -> 
PipelineDataNodeUtils.buildTableAndDataNodesMap(
+                database, Collections.singletonMap("foo_schema", 
Collections.singleton("foo_tbl"))));
+        assertThat(actual.getMessage(), is("There is invalid parameter value. 
Not find actual data nodes of `foo_tbl`"));
+        verify(laterOwner, never()).getDataNodesByTableName("foo_tbl");
+    }
+    
+    @Test
+    void assertBuildTableAndDataNodesMapWithEmptySchemaTableNames() {
+        PipelineInvalidParameterException actual = assertThrows(
+                PipelineInvalidParameterException.class, () -> 
PipelineDataNodeUtils.buildTableAndDataNodesMap(mock(ShardingSphereDatabase.class),
 Collections.emptyMap()));
+        assertThat(actual.getMessage(), is("There is invalid parameter value. 
Table names are empty."));
+    }
+    
+    @Test
+    void assertBuildTableAndDataNodesMapWithEmptyTableNames() {
+        PipelineInvalidParameterException actual = 
assertThrows(PipelineInvalidParameterException.class, () -> 
PipelineDataNodeUtils.buildTableAndDataNodesMap(
+                mock(ShardingSphereDatabase.class), 
Collections.singletonMap("foo_schema", Collections.emptySet())));
+        assertThat(actual.getMessage(), is("There is invalid parameter value. 
Table names are empty."));
     }
     
     @Test
     void assertBuildTableAndDataNodesMapWithNotExistedTable() {
+        PipelineInvalidParameterException actual = 
assertThrows(PipelineInvalidParameterException.class, () -> 
PipelineDataNodeUtils.buildTableAndDataNodesMap(
+                mockDatabase(Collections.emptyList()), 
Collections.singletonMap("", Collections.singleton("foo_tbl"))));
+        assertThat(actual.getMessage(), is("There is invalid parameter value. 
Not find actual data nodes of `foo_tbl`"));
+    }
+    
+    @Test
+    void assertBuildTableAndDataNodesMapWithDuplicateTableNameAcrossSchemas() {
+        DataNodeRuleAttribute ruleAttribute = mockRuleAttribute("foo_tbl", 
Collections.singleton(new DataNode("foo_ds.foo_tbl")), false, false);
+        ShardingSphereDatabase database = 
mockDatabase(Collections.singleton(ruleAttribute));
+        Map<String, Set<String>> schemaTableNames = new LinkedHashMap<>(2, 1F);
+        schemaTableNames.put("foo_schema", Collections.singleton("foo_tbl"));
+        schemaTableNames.put("bar_schema", Collections.singleton("foo_tbl"));
+        PipelineInvalidParameterException actual = assertThrows(
+                PipelineInvalidParameterException.class, () -> 
PipelineDataNodeUtils.buildTableAndDataNodesMap(database, schemaTableNames));
+        assertThat(actual.getMessage(), is("There is invalid parameter value. 
More than one schema table has the same table name `foo_tbl`."));
+        verify(ruleAttribute).getDataNodesByTableName("foo_tbl");
+    }
+    
+    @Test
+    void 
assertBuildTableAndDataNodesMapDoesNotNormalizeTableNamesAcrossSchemas() {
+        DataNodeRuleAttribute ruleAttribute = 
mock(DataNodeRuleAttribute.class);
+        DataNode fooDataNode = new DataNode("foo_ds.foo_tbl");
+        DataNode upperCaseFooDataNode = new DataNode("foo_ds.FOO_TBL");
+        
when(ruleAttribute.getDataNodesByTableName("foo_tbl")).thenReturn(Collections.singleton(fooDataNode));
+        
when(ruleAttribute.getDataNodesByTableName("FOO_TBL")).thenReturn(Collections.singleton(upperCaseFooDataNode));
+        ShardingSphereDatabase database = 
mockDatabase(Collections.singleton(ruleAttribute));
+        Map<String, Set<String>> schemaTableNames = new LinkedHashMap<>(2, 1F);
+        schemaTableNames.put("foo_schema", Collections.singleton("foo_tbl"));
+        schemaTableNames.put("bar_schema", Collections.singleton("FOO_TBL"));
+        Map<String, List<DataNode>> actual = 
PipelineDataNodeUtils.buildTableAndDataNodesMap(database, schemaTableNames);
+        assertThat(actual.get("foo_tbl"), 
is(Collections.singletonList(fooDataNode)));
+        assertThat(actual.get("FOO_TBL"), 
is(Collections.singletonList(upperCaseFooDataNode)));
+    }
+    
+    private ShardingSphereDatabase mockDatabase(final 
Collection<DataNodeRuleAttribute> ruleAttributes) {
         RuleMetaData ruleMetaData = mock(RuleMetaData.class);
-        
when(ruleMetaData.getAttributes(DataNodeRuleAttribute.class)).thenReturn(Collections.emptyList());
-        ShardingSphereDatabase database = mock(ShardingSphereDatabase.class);
-        when(database.getRuleMetaData()).thenReturn(ruleMetaData);
-        assertThrows(PipelineInvalidParameterException.class, () -> 
PipelineDataNodeUtils.buildTableAndDataNodesMap(database, 
Collections.singleton("foo_tbl")));
+        
when(ruleMetaData.getAttributes(DataNodeRuleAttribute.class)).thenReturn(ruleAttributes);
+        ShardingSphereDatabase result = mock(ShardingSphereDatabase.class);
+        when(result.getRuleMetaData()).thenReturn(ruleMetaData);
+        return result;
+    }
+    
+    private DataNodeRuleAttribute mockRuleAttribute(final String tableName, 
final Collection<DataNode> dataNodes,
+                                                    final boolean 
tableNameLoadedFromStorage, final boolean replicaBasedDistribution) {
+        DataNodeRuleAttribute result = mock(DataNodeRuleAttribute.class);
+        when(result.getDataNodesByTableName(tableName)).thenReturn(dataNodes);
+        if (tableNameLoadedFromStorage) {
+            
when(result.isDataNodeTableNameLoadedFromStorage()).thenReturn(true);
+        }
+        if (replicaBasedDistribution) {
+            when(result.isReplicaBasedDistribution()).thenReturn(true);
+        }
+        return result;
     }
 }
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 0f48322c2ee..61153669589 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
@@ -17,7 +17,6 @@
 
 package 
org.apache.shardingsphere.data.pipeline.postgresql.ingest.incremental.wal;
 
-import lombok.RequiredArgsConstructor;
 import 
org.apache.shardingsphere.data.pipeline.core.constant.PipelineSQLOperationType;
 import 
org.apache.shardingsphere.data.pipeline.core.ingest.dumper.incremental.IncrementalDumperContext;
 import org.apache.shardingsphere.data.pipeline.core.ingest.record.Column;
@@ -33,6 +32,10 @@ import 
org.apache.shardingsphere.data.pipeline.postgresql.ingest.incremental.wal
 import 
org.apache.shardingsphere.data.pipeline.postgresql.ingest.incremental.wal.event.DeleteRowEvent;
 import 
org.apache.shardingsphere.data.pipeline.postgresql.ingest.incremental.wal.event.UpdateRowEvent;
 import 
org.apache.shardingsphere.data.pipeline.postgresql.ingest.incremental.wal.event.WriteRowEvent;
+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.IdentifierNormalizeEngine;
+import 
org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
 import 
org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException;
 import 
org.apache.shardingsphere.infra.metadata.identifier.ShardingSphereIdentifier;
 
@@ -41,13 +44,27 @@ import java.util.List;
 /**
  * WAL event converter.
  */
-@RequiredArgsConstructor
 public final class WALEventConverter {
     
     private final IncrementalDumperContext dumperContext;
     
     private final PipelineTableMetaDataLoader metaDataLoader;
     
+    private final IdentifierCasePolicy schemaIdentifierCasePolicy;
+    
+    /**
+     * Create a WAL event converter.
+     *
+     * @param dumperContext incremental dumper context
+     * @param metaDataLoader pipeline table metadata loader
+     */
+    public WALEventConverter(final IncrementalDumperContext dumperContext, 
final PipelineTableMetaDataLoader metaDataLoader) {
+        this.dumperContext = dumperContext;
+        this.metaDataLoader = metaDataLoader;
+        schemaIdentifierCasePolicy = IdentifierNormalizeEngine.resolvePolicy(
+                
dumperContext.getCommonContext().getDataSourceConfig().getDatabaseType(), null, 
IdentifierScope.SCHEMA);
+    }
+    
     /**
      * Convert WAL event to {@code Record}.
      *
@@ -56,57 +73,68 @@ public final class WALEventConverter {
      * @throws UnsupportedSQLOperationException unsupported SQL operation 
exception
      */
     public Record convert(final AbstractWALEvent event) {
-        if (filter(event)) {
+        if (!(event instanceof AbstractRowEvent)) {
             return createPlaceholderRecord(event);
         }
-        if (!(event instanceof AbstractRowEvent)) {
+        AbstractRowEvent rowEvent = (AbstractRowEvent) event;
+        String actualTableName = rowEvent.getTableName();
+        ShardingSphereIdentifier logicTableName = 
dumperContext.getCommonContext().getTableNameMapper().getLogicTableName(actualTableName);
+        if (null == logicTableName) {
             return createPlaceholderRecord(event);
         }
-        PipelineTableMetaData tableMetaData = 
getPipelineTableMetaData(((AbstractRowEvent) event).getTableName());
+        String expectedSchemaName = 
dumperContext.getCommonContext().getTableAndSchemaNameMapper().getSchemaName(logicTableName);
+        if (isDifferentSchemaName(rowEvent, expectedSchemaName)) {
+            return createPlaceholderRecord(event);
+        }
+        PipelineTableMetaData tableMetaData = 
metaDataLoader.getTableMetaData(expectedSchemaName, actualTableName);
         if (event instanceof WriteRowEvent) {
-            return handleWriteRowEvent((WriteRowEvent) event, tableMetaData);
+            return handleWriteRowEvent((WriteRowEvent) event, tableMetaData, 
logicTableName);
         }
         if (event instanceof UpdateRowEvent) {
-            return handleUpdateRowEvent((UpdateRowEvent) event, tableMetaData);
+            return handleUpdateRowEvent((UpdateRowEvent) event, tableMetaData, 
logicTableName);
         }
         if (event instanceof DeleteRowEvent) {
-            return handleDeleteRowEvent((DeleteRowEvent) event, tableMetaData);
+            return handleDeleteRowEvent((DeleteRowEvent) event, tableMetaData, 
logicTableName);
         }
         throw new UnsupportedSQLOperationException("");
     }
     
-    private boolean filter(final AbstractWALEvent event) {
-        if (event instanceof AbstractRowEvent) {
-            AbstractRowEvent rowEvent = (AbstractRowEvent) event;
-            return 
!dumperContext.getCommonContext().getTableNameMapper().containsTable(rowEvent.getTableName());
+    private boolean isDifferentSchemaName(final AbstractRowEvent event, final 
String expectedSchemaName) {
+        if (!isConcreteSchemaName(expectedSchemaName)) {
+            return false;
         }
-        return false;
+        String actualSchemaName = event.getSchemaName();
+        if (!isConcreteSchemaName(actualSchemaName)) {
+            return false;
+        }
+        // The mapper stores a canonical schema name, while the WAL decoders 
preserve the event identifier's quote characters.
+        QuoteCharacter quoteCharacter = 
QuoteCharacter.getQuoteCharacter(actualSchemaName);
+        return !schemaIdentifierCasePolicy.matches(expectedSchemaName, 
quoteCharacter.unwrap(actualSchemaName), quoteCharacter);
     }
     
-    private PlaceholderRecord createPlaceholderRecord(final AbstractWALEvent 
event) {
-        return new PlaceholderRecord(new 
WALPosition(event.getLogSequenceNumber()));
+    private static boolean isConcreteSchemaName(final String schemaName) {
+        return null != schemaName && !schemaName.isEmpty() && 
!"*".equals(schemaName);
     }
     
-    private PipelineTableMetaData getPipelineTableMetaData(final String 
actualTableName) {
-        ShardingSphereIdentifier logicTableName = 
dumperContext.getCommonContext().getTableNameMapper().getLogicTableName(actualTableName);
-        return 
metaDataLoader.getTableMetaData(dumperContext.getCommonContext().getTableAndSchemaNameMapper().getSchemaName(logicTableName),
 actualTableName);
+    private PlaceholderRecord createPlaceholderRecord(final AbstractWALEvent 
event) {
+        return new PlaceholderRecord(new 
WALPosition(event.getLogSequenceNumber()));
     }
     
-    private DataRecord handleWriteRowEvent(final WriteRowEvent writeRowEvent, 
final PipelineTableMetaData tableMetaData) {
-        DataRecord result = createDataRecord(PipelineSQLOperationType.INSERT, 
writeRowEvent, writeRowEvent.getAfterRow().size());
+    private DataRecord handleWriteRowEvent(final WriteRowEvent writeRowEvent, 
final PipelineTableMetaData tableMetaData, final ShardingSphereIdentifier 
logicTableName) {
+        DataRecord result = createDataRecord(PipelineSQLOperationType.INSERT, 
writeRowEvent, logicTableName, writeRowEvent.getAfterRow().size());
         putColumnsIntoDataRecord(result, tableMetaData, 
writeRowEvent.getAfterRow());
         return result;
     }
     
-    private DataRecord handleUpdateRowEvent(final UpdateRowEvent 
updateRowEvent, final PipelineTableMetaData tableMetaData) {
-        DataRecord result = createDataRecord(PipelineSQLOperationType.UPDATE, 
updateRowEvent, updateRowEvent.getAfterRow().size());
+    private DataRecord handleUpdateRowEvent(final UpdateRowEvent 
updateRowEvent, final PipelineTableMetaData tableMetaData, final 
ShardingSphereIdentifier logicTableName) {
+        DataRecord result = createDataRecord(PipelineSQLOperationType.UPDATE, 
updateRowEvent, logicTableName, updateRowEvent.getAfterRow().size());
         putColumnsIntoDataRecord(result, tableMetaData, 
updateRowEvent.getAfterRow());
         return result;
     }
     
-    private DataRecord handleDeleteRowEvent(final DeleteRowEvent event, final 
PipelineTableMetaData tableMetaData) {
+    private DataRecord handleDeleteRowEvent(final DeleteRowEvent event, final 
PipelineTableMetaData tableMetaData, final ShardingSphereIdentifier 
logicTableName) {
         // TODO completion columns
-        DataRecord result = createDataRecord(PipelineSQLOperationType.DELETE, 
event, event.getPrimaryKeys().size());
+        DataRecord result = createDataRecord(PipelineSQLOperationType.DELETE, 
event, logicTableName, event.getPrimaryKeys().size());
         // TODO Unique key may be a column within unique index
         List<String> primaryKeyColumns = tableMetaData.getPrimaryKeyColumns();
         for (int i = 0; i < event.getPrimaryKeys().size(); i++) {
@@ -115,9 +143,8 @@ public final class WALEventConverter {
         return result;
     }
     
-    private DataRecord createDataRecord(final PipelineSQLOperationType type, 
final AbstractRowEvent rowsEvent, final int columnCount) {
-        String tableName = 
dumperContext.getCommonContext().getTableNameMapper().getLogicTableName(rowsEvent.getTableName()).toString();
-        DataRecord result = new DataRecord(type, rowsEvent.getSchemaName(), 
tableName, new WALPosition(rowsEvent.getLogSequenceNumber()), columnCount);
+    private DataRecord createDataRecord(final PipelineSQLOperationType type, 
final AbstractRowEvent rowsEvent, final ShardingSphereIdentifier 
logicTableName, final int columnCount) {
+        DataRecord result = new DataRecord(type, rowsEvent.getSchemaName(), 
logicTableName.toString(), new WALPosition(rowsEvent.getLogSequenceNumber()), 
columnCount);
         result.setActualTableName(rowsEvent.getTableName());
         result.setCsn(rowsEvent.getCsn());
         return result;
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 ed6c483fd4d..13e40c965c9 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
@@ -17,6 +17,7 @@
 
 package 
org.apache.shardingsphere.data.pipeline.postgresql.ingest.incremental.wal;
 
+import 
org.apache.shardingsphere.data.pipeline.api.PipelineDataSourceConfiguration;
 import 
org.apache.shardingsphere.data.pipeline.api.type.StandardPipelineDataSourceConfiguration;
 import 
org.apache.shardingsphere.data.pipeline.core.constant.PipelineSQLOperationType;
 import 
org.apache.shardingsphere.data.pipeline.core.datasource.PipelineDataSource;
@@ -28,6 +29,7 @@ import 
org.apache.shardingsphere.data.pipeline.core.ingest.dumper.mapper.TableAn
 import org.apache.shardingsphere.data.pipeline.core.ingest.record.DataRecord;
 import 
org.apache.shardingsphere.data.pipeline.core.ingest.record.PlaceholderRecord;
 import org.apache.shardingsphere.data.pipeline.core.ingest.record.Record;
+import 
org.apache.shardingsphere.data.pipeline.core.metadata.loader.PipelineTableMetaDataLoader;
 import 
org.apache.shardingsphere.data.pipeline.core.metadata.loader.StandardPipelineTableMetaDataLoader;
 import 
org.apache.shardingsphere.data.pipeline.core.metadata.model.PipelineColumnMetaData;
 import 
org.apache.shardingsphere.data.pipeline.core.metadata.model.PipelineTableMetaData;
@@ -39,15 +41,18 @@ import 
org.apache.shardingsphere.data.pipeline.postgresql.ingest.incremental.wal
 import 
org.apache.shardingsphere.data.pipeline.postgresql.ingest.incremental.wal.event.PlaceholderEvent;
 import 
org.apache.shardingsphere.data.pipeline.postgresql.ingest.incremental.wal.event.UpdateRowEvent;
 import 
org.apache.shardingsphere.data.pipeline.postgresql.ingest.incremental.wal.event.WriteRowEvent;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import 
org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException;
 import 
org.apache.shardingsphere.infra.metadata.identifier.ShardingSphereIdentifier;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.mockito.internal.configuration.plugins.Plugins;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.postgresql.replication.LogSequenceNumber;
 
-import java.lang.reflect.Method;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.sql.Statement;
@@ -60,14 +65,21 @@ import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.isA;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
 
 class WALEventConverterTest {
     
+    private final DatabaseType databaseType = 
TypedSPILoader.getService(DatabaseType.class, "PostgreSQL");
+    
     private WALEventConverter walEventConverter;
     
     private final LogSequenceNumber logSequenceNumber = 
LogSequenceNumber.valueOf("0/14EFDB8");
@@ -127,25 +139,20 @@ class WALEventConverterTest {
     }
     
     @Test
-    void assertWriteRowEvent() throws ReflectiveOperationException {
-        DataRecord actual = getDataRecord(createWriteRowEvent());
+    void assertWriteRowEvent() {
+        DataRecord actual = (DataRecord) 
walEventConverter.convert(createWriteRowEvent(""));
         assertThat(actual.getType(), is(PipelineSQLOperationType.INSERT));
         assertThat(actual.getColumnCount(), is(3));
     }
     
-    private WriteRowEvent createWriteRowEvent() {
+    private WriteRowEvent createWriteRowEvent(final String schemaName) {
         WriteRowEvent result = new WriteRowEvent();
-        result.setSchemaName("");
+        result.setSchemaName(schemaName);
         result.setTableName("t_order");
         result.setAfterRow(Arrays.asList(101, 1, "OK"));
         return result;
     }
     
-    private DataRecord getDataRecord(final WriteRowEvent rowsEvent) throws 
ReflectiveOperationException {
-        Method method = 
WALEventConverter.class.getDeclaredMethod("handleWriteRowEvent", 
WriteRowEvent.class, PipelineTableMetaData.class);
-        return (DataRecord) Plugins.getMemberAccessor().invoke(method, 
walEventConverter, rowsEvent, pipelineTableMetaData);
-    }
-    
     @Test
     void assertConvertBeginTXEvent() {
         BeginTXEvent beginTXEvent = new BeginTXEvent(100L, null);
@@ -196,6 +203,74 @@ class WALEventConverterTest {
         assertThat(walEventConverter.convert(mockUnknownTableEvent()), 
isA(PlaceholderRecord.class));
     }
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("provideDifferentConcreteSchemaNames")
+    void assertConvertWithDifferentConcreteSchema(final String name, final 
String expectedSchemaName, final String eventSchemaName) {
+        PipelineTableMetaDataLoader metaDataLoader = 
mock(PipelineTableMetaDataLoader.class);
+        when(metaDataLoader.getTableMetaData(expectedSchemaName, 
"t_order")).thenReturn(pipelineTableMetaData);
+        WALEventConverter converter = 
createSchemaAwareWALEventConverter(expectedSchemaName, metaDataLoader);
+        Record actual = 
converter.convert(createWriteRowEvent(eventSchemaName));
+        assertThat(actual, isA(PlaceholderRecord.class));
+        verifyNoInteractions(metaDataLoader);
+    }
+    
+    private static Stream<Arguments> provideDifferentConcreteSchemaNames() {
+        return Stream.of(
+                Arguments.of("different schema", "test", "public"),
+                Arguments.of("quoted upper-case schema", "public", 
"\"PUBLIC\""),
+                Arguments.of("quoted schema and unquoted token", "CaseSchema", 
"CaseSchema"),
+                Arguments.of("quoted and unquoted case collision", 
"CaseSchema", "caseschema"));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("provideSameConcreteSchemaNames")
+    void assertConvertWithSameConcreteSchema(final String name, final String 
expectedSchemaName, final String eventSchemaName) {
+        PipelineTableMetaDataLoader metaDataLoader = 
mock(PipelineTableMetaDataLoader.class);
+        when(metaDataLoader.getTableMetaData(expectedSchemaName, 
"t_order")).thenReturn(pipelineTableMetaData);
+        WALEventConverter converter = 
createSchemaAwareWALEventConverter(expectedSchemaName, metaDataLoader);
+        Record actual = 
converter.convert(createWriteRowEvent(eventSchemaName));
+        assertThat(actual, isA(DataRecord.class));
+        verify(metaDataLoader).getTableMetaData(expectedSchemaName, "t_order");
+    }
+    
+    private static Stream<Arguments> provideSameConcreteSchemaNames() {
+        return Stream.of(
+                Arguments.of("lower-case schema", "public", "public"),
+                Arguments.of("unquoted mixed-case token", "caseschema", 
"CaseSchema"),
+                Arguments.of("quoted upper-case schema", "PUBLIC", 
"\"PUBLIC\""),
+                Arguments.of("quoted mixed-case schema", "CaseSchema", 
"\"CaseSchema\""));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("provideUnspecifiedSchema")
+    void assertConvertWithUnspecifiedSchema(final String name, final String 
expectedSchemaName, final String eventSchemaName) {
+        PipelineTableMetaDataLoader metaDataLoader = 
mock(PipelineTableMetaDataLoader.class);
+        when(metaDataLoader.getTableMetaData(expectedSchemaName, 
"t_order")).thenReturn(pipelineTableMetaData);
+        WALEventConverter converter = 
createSchemaAwareWALEventConverter(expectedSchemaName, metaDataLoader);
+        Record actual = 
converter.convert(createWriteRowEvent(eventSchemaName));
+        assertThat(actual, isA(DataRecord.class));
+        verify(metaDataLoader).getTableMetaData(expectedSchemaName, "t_order");
+    }
+    
+    private static Stream<Arguments> provideUnspecifiedSchema() {
+        return Stream.of(
+                Arguments.of("null expected schema", null, "public"),
+                Arguments.of("empty expected schema", "", "public"),
+                Arguments.of("wildcard expected schema", "*", "public"),
+                Arguments.of("null event schema", "public", null),
+                Arguments.of("empty event schema", "public", ""),
+                Arguments.of("wildcard event schema", "public", "*"));
+    }
+    
+    private WALEventConverter createSchemaAwareWALEventConverter(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 
TableAndSchemaNameMapper(Collections.singletonMap("t_order", 
expectedSchemaName)));
+        return new WALEventConverter(new 
IncrementalDumperContext(commonContext, null, false), metaDataLoader);
+    }
+    
     @Test
     void assertConvertFailure() {
         AbstractRowEvent event = new AbstractRowEvent() {
diff --git 
a/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/api/CDCJobAPI.java
 
b/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/api/CDCJobAPI.java
index 0e59b6db65e..6d251ee5631 100644
--- 
a/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/api/CDCJobAPI.java
+++ 
b/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/api/CDCJobAPI.java
@@ -69,6 +69,7 @@ import 
org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
 import org.apache.shardingsphere.infra.instance.metadata.InstanceType;
 import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import 
org.apache.shardingsphere.infra.metadata.database.resource.unit.StorageUnit;
+import 
org.apache.shardingsphere.infra.metadata.identifier.ShardingSphereIdentifier;
 import org.apache.shardingsphere.infra.util.datetime.DateTimeFormatterFactory;
 import org.apache.shardingsphere.infra.util.yaml.YamlEngine;
 import org.apache.shardingsphere.infra.yaml.config.pojo.YamlRootConfiguration;
@@ -83,12 +84,14 @@ import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.Properties;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -136,6 +139,7 @@ public final class CDCJobAPI implements TransmissionJobAPI {
             log.warn("CDC job already exists in registry center, ignore, job 
id is `{}`", jobConfig.getJobId());
         } else {
             checkDataSources(jobConfig);
+            checkSchemaTableNames(jobConfig.getSchemaTableNames());
             
governanceFacade.getJobFacade().getJob().create(jobConfig.getJobId(), 
jobType.getOption().getJobClass());
             JobConfigurationPOJO jobConfigPOJO = 
jobConfigManager.convertToJobConfigurationPOJO(jobConfig);
             jobConfigPOJO.setDisabled(true);
@@ -159,6 +163,15 @@ public final class CDCJobAPI implements TransmissionJobAPI 
{
         return jobConfig.getJobShardingDataNodes().stream().flatMap(each -> 
each.getEntries().stream()).flatMap(dataNodeEntry -> 
dataNodeEntry.getDataNodes().stream()).collect(Collectors.toList());
     }
     
+    private void checkSchemaTableNames(final Collection<String> 
schemaTableNames) {
+        Set<ShardingSphereIdentifier> tableNames = new 
HashSet<>(schemaTableNames.size(), 1F);
+        for (String each : schemaTableNames) {
+            String tableName = each.substring(each.lastIndexOf('.') + 1);
+            ShardingSpherePreconditions.checkState(tableNames.add(new 
ShardingSphereIdentifier(tableName)),
+                    () -> new 
PipelineInvalidParameterException(String.format("More than one schema table has 
the same table name `%s`.", tableName)));
+        }
+    }
+    
     private YamlCDCJobConfiguration getYamlCDCJobConfiguration(final 
StreamDataParameter param, final CDCSinkType sinkType, final Properties 
sinkProps, final PipelineContextKey contextKey) {
         YamlCDCJobConfiguration result = new YamlCDCJobConfiguration();
         List<String> schemaTableNames = param.getSchemaTableNames();
diff --git 
a/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/handler/CDCBackendHandler.java
 
b/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/handler/CDCBackendHandler.java
index 49bfd12beb3..7167f58dd79 100644
--- 
a/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/handler/CDCBackendHandler.java
+++ 
b/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/handler/CDCBackendHandler.java
@@ -38,7 +38,6 @@ import 
org.apache.shardingsphere.data.pipeline.cdc.exception.StreamDatabaseNotFo
 import org.apache.shardingsphere.data.pipeline.cdc.generator.CDCResponseUtils;
 import 
org.apache.shardingsphere.data.pipeline.cdc.protocol.request.AckStreamingRequestBody;
 import 
org.apache.shardingsphere.data.pipeline.cdc.protocol.request.StreamDataRequestBody;
-import 
org.apache.shardingsphere.data.pipeline.cdc.protocol.request.StreamDataRequestBody.SchemaTable;
 import 
org.apache.shardingsphere.data.pipeline.cdc.protocol.response.CDCResponse;
 import 
org.apache.shardingsphere.data.pipeline.cdc.protocol.response.CDCResponse.ResponseCase;
 import 
org.apache.shardingsphere.data.pipeline.cdc.protocol.response.StreamDataResult;
@@ -51,7 +50,6 @@ import 
org.apache.shardingsphere.data.pipeline.core.job.api.TransmissionJobAPI;
 import org.apache.shardingsphere.data.pipeline.core.job.id.PipelineJobIdUtils;
 import 
org.apache.shardingsphere.data.pipeline.core.job.service.PipelineJobConfigurationManager;
 import org.apache.shardingsphere.data.pipeline.core.util.PipelineDataNodeUtils;
-import 
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
 import 
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
 import org.apache.shardingsphere.infra.datanode.DataNode;
 import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
@@ -59,13 +57,11 @@ import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * CDC backend handler.
@@ -100,22 +96,12 @@ public final class CDCBackendHandler {
         ShardingSphereDatabase database = 
PipelineContextManager.getProxyContext().getMetaDataContexts().getMetaData().getDatabase(requestBody.getDatabase());
         ShardingSpherePreconditions.checkNotNull(database,
                 () -> new CDCExceptionWrapper(requestId, new 
StreamDatabaseNotFoundException(String.format("%s database is not exists", 
requestBody.getDatabase()))));
-        Map<String, Set<String>> schemaTableNameMap;
-        Collection<String> tableNames;
+        Map<String, Set<String>> schemaTableNameMap = 
CDCSchemaTableUtils.parseTableExpressions(database, 
requestBody.getSourceSchemaTableList());
         Set<String> schemaTableNames = new HashSet<>();
-        DialectDatabaseMetaData dialectDatabaseMetaData = new 
DatabaseTypeRegistry(database.getProtocolType()).getDialectDatabaseMetaData();
-        if (dialectDatabaseMetaData.getSchemaOption().isSchemaAvailable()) {
-            schemaTableNameMap = 
CDCSchemaTableUtils.parseTableExpressionWithSchema(database, 
requestBody.getSourceSchemaTableList());
-            // TODO if different schema have same table names, table name may 
be overwritten, because the table name at sharding rule not contain schema.
-            tableNames = 
schemaTableNameMap.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
-            schemaTableNameMap.forEach((key, value) -> value.forEach(tableName 
-> schemaTableNames.add(key.isEmpty() ? tableName : String.join(".", key, 
tableName))));
-        } else {
-            
schemaTableNames.addAll(CDCSchemaTableUtils.parseTableExpressionWithoutSchema(database,
-                    
requestBody.getSourceSchemaTableList().stream().map(SchemaTable::getTable).collect(Collectors.toList())));
-            tableNames = schemaTableNames;
-        }
-        ShardingSpherePreconditions.checkNotEmpty(tableNames, () -> new 
CDCExceptionWrapper(requestId, new MissingRequiredStreamDataSourceException()));
-        Map<String, List<DataNode>> tableAndDataNodesMap = 
PipelineDataNodeUtils.buildTableAndDataNodesMap(database, tableNames);
+        schemaTableNameMap.forEach((schemaName, tableNames) -> 
tableNames.forEach(
+                tableName -> schemaTableNames.add(schemaName.isEmpty() ? 
tableName : String.join(".", schemaName, tableName))));
+        ShardingSpherePreconditions.checkNotEmpty(schemaTableNames, () -> new 
CDCExceptionWrapper(requestId, new MissingRequiredStreamDataSourceException()));
+        Map<String, List<DataNode>> tableAndDataNodesMap = 
PipelineDataNodeUtils.buildTableAndDataNodesMap(database, schemaTableNameMap);
         // TODO Add globalCSNSupported to isolate it with 
isDecodeWithTransaction flag, they're different. And also update CDCJobPreparer 
needSorting flag.
         boolean isDecodeWithTransaction = new 
DatabaseTypeRegistry(database.getProtocolType()).getDialectDatabaseMetaData().getTransactionOption().isSupportGlobalCSN();
         StreamDataParameter parameter = new 
StreamDataParameter(requestBody.getDatabase(), new 
ArrayList<>(schemaTableNames), requestBody.getFull(), tableAndDataNodesMap, 
isDecodeWithTransaction);
diff --git 
a/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/util/CDCSchemaTableUtils.java
 
b/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/util/CDCSchemaTableUtils.java
index 44aaf2d6ed3..ba986b053bb 100644
--- 
a/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/util/CDCSchemaTableUtils.java
+++ 
b/kernel/data-pipeline/scenario/cdc/core/src/main/java/org/apache/shardingsphere/data/pipeline/cdc/util/CDCSchemaTableUtils.java
@@ -30,12 +30,12 @@ import 
org.apache.shardingsphere.infra.exception.kernel.metadata.TableNotFoundEx
 import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
 import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
+import 
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
 
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -47,19 +47,26 @@ import java.util.stream.Collectors;
 public final class CDCSchemaTableUtils {
     
     /**
-     * Parse table expression with schema.
+     * Parse table expressions.
      *
      * @param database database
      * @param schemaTables schema tables
-     * @return map key is schema, value is table names
+     * @return map of resolved schema names to table names; the schema name is 
empty only for database protocols without schema
      */
-    public static Map<String, Set<String>> 
parseTableExpressionWithSchema(final ShardingSphereDatabase database, final 
Collection<SchemaTable> schemaTables) {
+    public static Map<String, Set<String>> parseTableExpressions(final 
ShardingSphereDatabase database, final Collection<SchemaTable> schemaTables) {
+        DialectDatabaseMetaData dialectDatabaseMetaData = new 
DatabaseTypeRegistry(database.getProtocolType()).getDialectDatabaseMetaData();
+        return dialectDatabaseMetaData.getSchemaOption().isSchemaAvailable()
+                ? parseTableExpressionsWithSchema(database, schemaTables, 
dialectDatabaseMetaData)
+                : parseTableExpressionsWithoutSchema(database, schemaTables);
+    }
+    
+    private static Map<String, Set<String>> 
parseTableExpressionsWithSchema(final ShardingSphereDatabase database, final 
Collection<SchemaTable> schemaTables,
+                                                                            
final DialectDatabaseMetaData dialectDatabaseMetaData) {
         Collection<String> systemSchemas = 
DatabaseTypedSPILoader.getService(DialectSystemDatabase.class, 
database.getProtocolType()).getSystemSchemas();
         if (schemaTables.stream().anyMatch(each -> "*".equals(each.getTable()) 
&& ("*".equals(each.getSchema()) || each.getSchema().isEmpty()))) {
             return parseTableExpressionWithAllTables(database, systemSchemas);
         }
         Map<String, Set<String>> result = new HashMap<>();
-        DialectDatabaseMetaData dialectDatabaseMetaData = new 
DatabaseTypeRegistry(database.getProtocolType()).getDialectDatabaseMetaData();
         for (SchemaTable each : schemaTables) {
             if ("*".equals(each.getSchema())) {
                 result.putAll(parseTableExpressionWithAllSchema(database, 
systemSchemas, each));
@@ -67,11 +74,12 @@ public final class CDCSchemaTableUtils {
                 result.putAll(parseTableExpressionWithAllTable(database, 
each));
             } else {
                 String schemaName = each.getSchema();
-                if 
(dialectDatabaseMetaData.getSchemaOption().getDefaultSchema().isPresent() && 
schemaName.isEmpty()) {
-                    schemaName = 
dialectDatabaseMetaData.getSchemaOption().getDefaultSchema().get();
+                if (schemaName.isEmpty()) {
+                    schemaName = 
dialectDatabaseMetaData.getSchemaOption().getDefaultSchema().orElse("");
                 }
-                
ShardingSpherePreconditions.checkNotNull(database.getSchema(schemaName).getTable(each.getTable()),
 () -> new TableNotFoundException(each.getTable()));
-                result.computeIfAbsent(schemaName, ignored -> new 
HashSet<>()).add(each.getTable());
+                ShardingSphereSchema schema = database.getSchema(new 
IdentifierValue(schemaName));
+                
ShardingSpherePreconditions.checkNotNull(schema.getTable(each.getTable()), () 
-> new TableNotFoundException(each.getTable()));
+                result.computeIfAbsent(schema.getName(), ignored -> new 
HashSet<>()).add(each.getTable());
             }
         }
         return result;
@@ -100,24 +108,27 @@ public final class CDCSchemaTableUtils {
     
     private static Map<String, Set<String>> 
parseTableExpressionWithAllTable(final ShardingSphereDatabase database, final 
SchemaTable schemaTable) {
         String schemaName = schemaTable.getSchema();
-        ShardingSphereSchema schema = database.getSchema(schemaName);
+        ShardingSphereSchema schema = database.getSchema(new 
IdentifierValue(schemaName));
         ShardingSpherePreconditions.checkNotNull(schema, () -> new 
SchemaNotFoundException(schemaTable.getSchema()));
         Collection<ShardingSphereTable> tables = schema.getAllTables();
         Map<String, Set<String>> result = new HashMap<>(tables.size(), 1F);
-        tables.forEach(each -> result.computeIfAbsent(schemaName, ignored -> 
new HashSet<>()).add(each.getName()));
+        tables.forEach(each -> result.computeIfAbsent(schema.getName(), 
ignored -> new HashSet<>()).add(each.getName()));
         return result;
     }
     
-    /**
-     * Parse table expression without schema.
-     *
-     * @param database database
-     * @param tableNames table names
-     * @return parsed table names
-     */
-    public static Collection<String> parseTableExpressionWithoutSchema(final 
ShardingSphereDatabase database, final List<String> tableNames) {
+    private static Map<String, Set<String>> 
parseTableExpressionsWithoutSchema(final ShardingSphereDatabase database, final 
Collection<SchemaTable> schemaTables) {
+        Set<String> tableNames = 
schemaTables.stream().map(SchemaTable::getTable).collect(Collectors.toSet());
+        if (tableNames.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        if (!tableNames.contains("*")) {
+            return Collections.singletonMap("", tableNames);
+        }
         ShardingSphereSchema schema = database.getSchema(database.getName());
-        Set<String> allTableNames = null == schema ? Collections.emptySet() : 
new 
HashSet<>(schema.getAllTables().stream().map(ShardingSphereTable::getName).collect(Collectors.toSet()));
-        return tableNames.stream().anyMatch("*"::equals) ? allTableNames : new 
HashSet<>(tableNames);
+        if (null == schema) {
+            return Collections.emptyMap();
+        }
+        Set<String> allTableNames = 
schema.getAllTables().stream().map(ShardingSphereTable::getName).collect(Collectors.toSet());
+        return allTableNames.isEmpty() ? Collections.emptyMap() : 
Collections.singletonMap("", allTableNames);
     }
 }
diff --git 
a/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/api/CDCJobAPITest.java
 
b/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/api/CDCJobAPITest.java
index b4498696515..f23a98eb03d 100644
--- 
a/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/api/CDCJobAPITest.java
+++ 
b/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/api/CDCJobAPITest.java
@@ -197,14 +197,15 @@ class CDCJobAPITest {
     @Test
     void assertCreateSkipsExistingJob() {
         putContext(Collections.singletonMap("foo_ds", 
mock(StorageUnit.class)));
-        CDCJobConfiguration jobConfig = 
createJobConfiguration(Collections.singletonList("foo_readwrite_ds"), true);
+        List<String> schemaTableNames = Arrays.asList("foo_schema.foo_tbl", 
"bar_schema.FOO_TBL");
+        CDCJobConfiguration jobConfig = 
createJobConfiguration(Collections.singletonList("foo_readwrite_ds"), true, 
schemaTableNames);
         PipelineGovernanceFacade governanceFacade = 
mock(PipelineGovernanceFacade.class, RETURNS_DEEP_STUBS);
         
when(governanceFacade.getJobFacade().getConfiguration().isExisted("foo_job")).thenReturn(true);
         
when(PipelineAPIFactory.getPipelineGovernanceFacade(any())).thenReturn(governanceFacade);
         try (
                 MockedConstruction<YamlCDCJobConfigurationSwapper> ignored = 
mockConstruction(YamlCDCJobConfigurationSwapper.class,
                         (mock, context) -> 
when(mock.swapToObject(any(YamlCDCJobConfiguration.class))).thenReturn(jobConfig)))
 {
-            StreamDataParameter param = new StreamDataParameter("foo_db", new 
LinkedList<>(Collections.singletonList("foo_schema.foo_tbl")), true,
+            StreamDataParameter param = new StreamDataParameter("foo_db", new 
LinkedList<>(schemaTableNames), true,
                     Collections.singletonMap("foo_schema.foo_tbl", 
Collections.singletonList(new DataNode("foo_readwrite_ds.foo_tbl"))), false);
             assertThat(jobAPI.create(param, CDCSinkType.SOCKET, new 
Properties()), is("foo_job"));
             verify(governanceFacade.getJobFacade().getJob(), 
never()).create(anyString(), any());
@@ -215,7 +216,8 @@ class CDCJobAPITest {
     @Test
     void assertCreateThrowsWhenDataSourceDoesNotExist() throws 
ReflectiveOperationException {
         putContext(Collections.singletonMap("foo_ds", 
mock(StorageUnit.class)));
-        CDCJobConfiguration jobConfig = 
createJobConfiguration(Arrays.asList("bar_ds", "foo_readwrite_ds"), true);
+        List<String> schemaTableNames = Arrays.asList("foo_schema.foo_tbl", 
"bar_schema.FOO_TBL");
+        CDCJobConfiguration jobConfig = 
createJobConfiguration(Arrays.asList("bar_ds", "foo_readwrite_ds"), true, 
schemaTableNames);
         PipelineGovernanceFacade governanceFacade = 
mock(PipelineGovernanceFacade.class, RETURNS_DEEP_STUBS);
         
when(PipelineAPIFactory.getPipelineGovernanceFacade(any())).thenReturn(governanceFacade);
         PipelineJobConfigurationManager jobConfigManager = 
mock(PipelineJobConfigurationManager.class);
@@ -224,7 +226,7 @@ class CDCJobAPITest {
         try (
                 MockedConstruction<YamlCDCJobConfigurationSwapper> ignored = 
mockConstruction(YamlCDCJobConfigurationSwapper.class,
                         (mock, context) -> 
when(mock.swapToObject(any(YamlCDCJobConfiguration.class))).thenReturn(jobConfig)))
 {
-            StreamDataParameter param = new StreamDataParameter("foo_db", new 
LinkedList<>(Collections.singletonList("foo_schema.foo_tbl")), true,
+            StreamDataParameter param = new StreamDataParameter("foo_db", new 
LinkedList<>(schemaTableNames), true,
                     Collections.singletonMap("foo_schema.foo_tbl", 
Arrays.asList(new DataNode("bar_ds.foo_tbl"), new 
DataNode("foo_readwrite_ds.foo_tbl"))), false);
             PipelineInvalidParameterException actual = 
assertThrows(PipelineInvalidParameterException.class, () -> 
jobAPI.create(param, CDCSinkType.SOCKET, new Properties()));
             assertThat(actual.getMessage(), 
containsString("foo_readwrite_ds"));
@@ -233,6 +235,25 @@ class CDCJobAPITest {
         }
     }
     
+    @Test
+    void assertCreateThrowsWhenSchemaTableNamesHaveDuplicateBareTable() {
+        putContext(Collections.singletonMap("foo_ds", 
mock(StorageUnit.class)));
+        List<String> schemaTableNames = Arrays.asList("schema_a.t", 
"schema_b.T");
+        CDCJobConfiguration jobConfig = 
createJobConfiguration(Collections.singletonList("foo_ds"), true, 
schemaTableNames);
+        PipelineGovernanceFacade governanceFacade = 
mock(PipelineGovernanceFacade.class, RETURNS_DEEP_STUBS);
+        
when(PipelineAPIFactory.getPipelineGovernanceFacade(any())).thenReturn(governanceFacade);
+        try (
+                MockedConstruction<YamlCDCJobConfigurationSwapper> ignored = 
mockConstruction(YamlCDCJobConfigurationSwapper.class,
+                        (mock, context) -> 
when(mock.swapToObject(any(YamlCDCJobConfiguration.class))).thenReturn(jobConfig)))
 {
+            StreamDataParameter param = new StreamDataParameter("foo_db", new 
LinkedList<>(schemaTableNames), true,
+                    Collections.singletonMap("t", 
Collections.singletonList(new DataNode("foo_ds.t"))), false);
+            PipelineInvalidParameterException actual = 
assertThrows(PipelineInvalidParameterException.class, () -> 
jobAPI.create(param, CDCSinkType.SOCKET, new Properties()));
+            assertThat(actual.getMessage(), is("There is invalid parameter 
value. More than one schema table has the same table name `T`."));
+            verify(governanceFacade.getJobFacade().getJob(), 
never()).create(anyString(), any());
+            verify(governanceFacade.getJobFacade().getConfiguration(), 
never()).persist(anyString(), any());
+        }
+    }
+    
     @Test
     void assertCreateFullJobSkipsInitIncrementalPosition() throws 
ReflectiveOperationException {
         putContext(Collections.singletonMap("foo_ds", 
mock(StorageUnit.class)));
@@ -441,6 +462,10 @@ class CDCJobAPITest {
     }
     
     private CDCJobConfiguration createJobConfiguration(final List<String> 
dataSourceNames, final boolean full) {
+        return createJobConfiguration(dataSourceNames, full, 
Collections.singletonList("foo_schema.foo_tbl"));
+    }
+    
+    private CDCJobConfiguration createJobConfiguration(final List<String> 
dataSourceNames, final boolean full, final List<String> schemaTableNames) {
         Map<String, Map<String, Object>> dataSources = new LinkedHashMap<>(2, 
1F);
         dataSources.put("foo_ds", createStandardDataSourceProperties());
         dataSources.put("bar_ds", createStandardDataSourceProperties());
@@ -448,7 +473,7 @@ class CDCJobAPITest {
         List<JobDataNodeLine> jobDataNodeLines = dataSourceNames.stream().map(
                 each -> new JobDataNodeLine(Collections.singletonList(new 
JobDataNodeEntry("foo_tbl", Collections.singletonList(new DataNode(each + 
".foo_tbl"))))))
                 .collect(Collectors.toList());
-        return new CDCJobConfiguration("foo_job", "foo_db", 
Collections.singletonList("foo_schema.foo_tbl"), full, mock(),
+        return new CDCJobConfiguration("foo_job", "foo_db", schemaTableNames, 
full, mock(),
                 dataSourceConfig, jobDataNodeLines.get(0), jobDataNodeLines, 
false, new CDCJobConfiguration.SinkConfiguration(CDCSinkType.SOCKET, new 
Properties()), 1, 0);
     }
     
diff --git 
a/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/handler/CDCBackendHandlerTest.java
 
b/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/handler/CDCBackendHandlerTest.java
index 77599c0cfd4..44126f1f566 100644
--- 
a/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/handler/CDCBackendHandlerTest.java
+++ 
b/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/handler/CDCBackendHandlerTest.java
@@ -67,8 +67,9 @@ import org.mockito.internal.configuration.plugins.Plugins;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -76,7 +77,6 @@ import static org.hamcrest.Matchers.isA;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyCollection;
-import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.lenient;
@@ -117,10 +117,11 @@ class CDCBackendHandlerTest {
     @Test
     void assertStreamDataWhenSchemaAvailable() {
         ShardingSphereDatabase database = mockDatabase();
-        mockDialectMetaData(true, false);
-        when(CDCSchemaTableUtils.parseTableExpressionWithSchema(eq(database), 
anyCollection()))
-                .thenReturn(Collections.singletonMap("foo_schema", new 
LinkedHashSet<>(Collections.singleton("foo_tbl"))));
-        when(PipelineDataNodeUtils.buildTableAndDataNodesMap(eq(database), 
anyCollection())).thenReturn(Collections.singletonMap("foo_tbl", 
Collections.singletonList(mock(DataNode.class))));
+        mockDialectMetaData(false);
+        Map<String, Set<String>> schemaTableNames = 
Collections.singletonMap("foo_schema", Collections.singleton("foo_tbl"));
+        when(CDCSchemaTableUtils.parseTableExpressions(eq(database), 
anyCollection())).thenReturn(schemaTableNames);
+        DataNode fooDataNode = new DataNode("foo_ds", "foo_schema", "foo_tbl");
+        when(PipelineDataNodeUtils.buildTableAndDataNodesMap(database, 
schemaTableNames)).thenReturn(Collections.singletonMap("foo_tbl", 
Collections.singletonList(fooDataNode)));
         when(jobAPI.create(any(StreamDataParameter.class), 
eq(CDCSinkType.SOCKET), any(Properties.class))).thenReturn("foo_job");
         
when(jobConfigManager.getJobConfiguration("foo_job")).thenReturn(createJobConfiguration());
         StreamDataRequestBody requestBody = 
StreamDataRequestBody.newBuilder().setDatabase("foo_db")
@@ -129,30 +130,40 @@ class CDCBackendHandlerTest {
         assertThat(actualResponse.getStatus(), is(CDCResponse.Status.SUCCEED));
         assertThat(actualResponse.getResponseCase(), 
is(CDCResponse.ResponseCase.STREAM_DATA_RESULT));
         assertThat(actualResponse.getStreamDataResult().getStreamingId(), 
is("foo_job"));
+        ArgumentCaptor<StreamDataParameter> parameterCaptor = 
ArgumentCaptor.forClass(StreamDataParameter.class);
+        verify(jobAPI).create(parameterCaptor.capture(), 
eq(CDCSinkType.SOCKET), any(Properties.class));
+        assertThat(parameterCaptor.getValue().getSchemaTableNames(), 
is(Collections.singletonList("foo_schema.foo_tbl")));
+        
assertThat(parameterCaptor.getValue().getTableAndDataNodesMap().get("foo_tbl"), 
is(Collections.singletonList(fooDataNode)));
     }
     
     @Test
     void assertStreamDataWithoutSchema() {
         ShardingSphereDatabase database = mockDatabase();
-        mockDialectMetaData(false, true);
-        
when(CDCSchemaTableUtils.parseTableExpressionWithoutSchema(eq(database), 
anyList())).thenReturn(Collections.singleton("foo_tbl"));
-        when(PipelineDataNodeUtils.buildTableAndDataNodesMap(eq(database), 
anyCollection())).thenReturn(Collections.singletonMap("foo_tbl", 
Collections.singletonList(mock(DataNode.class))));
+        mockDialectMetaData(true);
+        Map<String, Set<String>> schemaTableNames = 
Collections.singletonMap("", Collections.singleton("foo_tbl"));
+        when(CDCSchemaTableUtils.parseTableExpressions(eq(database), 
anyCollection())).thenReturn(schemaTableNames);
+        when(PipelineDataNodeUtils.buildTableAndDataNodesMap(database, 
schemaTableNames)).thenReturn(Collections.singletonMap("foo_tbl", 
Collections.singletonList(mock(DataNode.class))));
         when(jobAPI.create(any(StreamDataParameter.class), 
eq(CDCSinkType.SOCKET), any(Properties.class))).thenReturn("foo_job");
         
when(jobConfigManager.getJobConfiguration("foo_job")).thenReturn(createJobConfiguration());
         StreamDataRequestBody requestBody = 
StreamDataRequestBody.newBuilder().setDatabase("foo_db").addSourceSchemaTable(SchemaTable.newBuilder().setTable("foo_tbl")).build();
         CDCResponse actualResponse = backendHandler.streamData("foo_req", 
requestBody, createConnectionContext(), mock());
         assertThat(actualResponse.getStatus(), is(CDCResponse.Status.SUCCEED));
         assertThat(actualResponse.getStreamDataResult().getStreamingId(), 
is("foo_job"));
+        ArgumentCaptor<StreamDataParameter> parameterCaptor = 
ArgumentCaptor.forClass(StreamDataParameter.class);
+        verify(jobAPI).create(parameterCaptor.capture(), 
eq(CDCSinkType.SOCKET), any(Properties.class));
+        assertThat(parameterCaptor.getValue().getSchemaTableNames(), 
is(Collections.singletonList("foo_tbl")));
     }
     
     @Test
     void assertStreamDataWhenTableNamesMissing() {
-        ShardingSphereDatabase database = mockDatabase();
-        mockDialectMetaData(false, false);
-        
when(CDCSchemaTableUtils.parseTableExpressionWithoutSchema(eq(database), 
anyList())).thenReturn(Collections.emptySet());
+        ShardingSphereDatabase database = mock(ShardingSphereDatabase.class);
+        mockProxyContext(database);
+        mockDialectMetaData(false);
+        when(CDCSchemaTableUtils.parseTableExpressions(eq(database), 
anyCollection())).thenReturn(Collections.emptyMap());
         StreamDataRequestBody requestBody = 
StreamDataRequestBody.newBuilder().setDatabase("foo_db").addSourceSchemaTable(SchemaTable.newBuilder().setTable("foo_tbl")).build();
         CDCExceptionWrapper actualException = 
assertThrows(CDCExceptionWrapper.class, () -> 
backendHandler.streamData("foo_req", requestBody, createConnectionContext(), 
mock()));
         assertThat(actualException.getCause(), 
isA(MissingRequiredStreamDataSourceException.class));
+        verifyNoInteractions(jobAPI);
     }
     
     @Test
@@ -268,9 +279,8 @@ class CDCBackendHandlerTest {
         
when(PipelineContextManager.getProxyContext()).thenReturn(contextManager);
     }
     
-    private void mockDialectMetaData(final boolean schemaAvailable, final 
boolean supportGlobalCSN) {
+    private void mockDialectMetaData(final boolean supportGlobalCSN) {
         DialectDatabaseMetaData databaseMetaData = 
mock(DialectDatabaseMetaData.class, RETURNS_DEEP_STUBS);
-        
when(databaseMetaData.getSchemaOption().isSchemaAvailable()).thenReturn(schemaAvailable);
         
lenient().when(databaseMetaData.getTransactionOption().isSupportGlobalCSN()).thenReturn(supportGlobalCSN);
         when(DatabaseTypedSPILoader.getService(any(), 
any())).thenReturn(databaseMetaData);
     }
diff --git 
a/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/util/CDCSchemaTableUtilsTest.java
 
b/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/util/CDCSchemaTableUtilsTest.java
index 1796c0a8532..05500aeed79 100644
--- 
a/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/util/CDCSchemaTableUtilsTest.java
+++ 
b/kernel/data-pipeline/scenario/cdc/core/src/test/java/org/apache/shardingsphere/data/pipeline/cdc/util/CDCSchemaTableUtilsTest.java
@@ -27,6 +27,9 @@ import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSp
 import 
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -38,6 +41,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.Properties;
+import java.util.stream.Stream;
 
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -50,14 +54,14 @@ class CDCSchemaTableUtilsTest {
     private final DatabaseType databaseType = 
TypedSPILoader.getService(DatabaseType.class, "openGauss");
     
     @Test
-    void assertParseTableExpressionWithFullWildcard() {
+    void assertParseTableExpressionsWithFullWildcard() {
         ShardingSphereSchema publicSchema = mockSchema("public", "t_order", 
"t_order_item");
         ShardingSphereSchema testSchema = mockSchema("test", "t_test");
         ShardingSphereSchema systemSchema = mockSchema("pg_catalog", "t_pg");
         ShardingSphereDatabase database =
                 new ShardingSphereDatabase("sharding_db", databaseType, null, 
null, Arrays.asList(publicSchema, testSchema, systemSchema), new 
ConfigurationProperties(new Properties()));
         List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setSchema("*").setTable("*").build());
-        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressionWithSchema(database, schemaTables);
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(database, schemaTables);
         Map<String, Set<String>> expectedResult = new HashMap<>(2, 1F);
         expectedResult.put("public", new HashSet<>(Arrays.asList("t_order", 
"t_order_item")));
         expectedResult.put("test", Collections.singleton("t_test"));
@@ -65,61 +69,139 @@ class CDCSchemaTableUtilsTest {
     }
     
     @Test
-    void assertParseTableExpressionWithSchemaWildcard() {
+    void assertParseTableExpressionsWithUnqualifiedFullWildcard() {
+        ShardingSphereSchema publicSchema = mockSchema("public", "t_order");
+        ShardingSphereDatabase database =
+                new ShardingSphereDatabase("sharding_db", databaseType, null, 
null, Collections.singleton(publicSchema), new ConfigurationProperties(new 
Properties()));
+        List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setTable("*").build());
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(database, schemaTables);
+        Map<String, Set<String>> expectedResult = 
Collections.singletonMap("public", Collections.singleton("t_order"));
+        assertThat(actualResult, is(expectedResult));
+    }
+    
+    @Test
+    void assertParseTableExpressionsWithSchemaWildcard() {
         ShardingSphereSchema analyticsSchema = mockSchema("analytics", 
"t_shared");
         ShardingSphereSchema auditSchema = mockSchema("audit", "t_other");
         ShardingSphereSchema systemSchema = mockSchema("pg_catalog", 
"t_shared");
         ShardingSphereDatabase database =
                 new ShardingSphereDatabase("sharding_db", databaseType, null, 
null, Arrays.asList(analyticsSchema, auditSchema, systemSchema), new 
ConfigurationProperties(new Properties()));
         List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setSchema("*").setTable("t_shared").build());
-        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressionWithSchema(database, schemaTables);
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(database, schemaTables);
         Map<String, Set<String>> expectedResult = 
Collections.singletonMap("analytics", Collections.singleton("t_shared"));
         assertThat(actualResult, is(expectedResult));
     }
     
     @Test
-    void assertParseTableExpressionWithAllTablesInSchema() {
-        ShardingSphereSchema publicSchema = mockSchema("public", "t_order", 
"t_order_item");
-        ShardingSphereDatabase database = new 
ShardingSphereDatabase("sharding_db", databaseType, null, null, 
Collections.singleton(publicSchema), new ConfigurationProperties(new 
Properties()));
-        List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setSchema("public").setTable("*").build());
-        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressionWithSchema(database, schemaTables);
-        Map<String, Set<String>> expectedResult = 
Collections.singletonMap("public", new HashSet<>(Arrays.asList("t_order", 
"t_order_item")));
+    void assertParseTableExpressionsWithAllTablesInSchema() {
+        ShardingSphereSchema quotedSchema = mockSchema("CaseSchema", 
"t_order", "t_order_item");
+        ShardingSphereDatabase database = new 
ShardingSphereDatabase("sharding_db", databaseType, null, null, 
Collections.singleton(quotedSchema), new ConfigurationProperties(new 
Properties()));
+        List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setSchema("\"CaseSchema\"").setTable("*").build());
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(database, schemaTables);
+        Map<String, Set<String>> expectedResult = 
Collections.singletonMap("CaseSchema", new HashSet<>(Arrays.asList("t_order", 
"t_order_item")));
+        assertThat(actualResult, is(expectedResult));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("provideSchemaNames")
+    void assertParseTableExpressionsResolvesCanonicalSchemaName(final String 
name, final String inputSchemaName, final String expectedSchemaName) {
+        ShardingSphereSchema schema = mockSchema(expectedSchemaName, 
"t_order");
+        ShardingSphereDatabase database =
+                new ShardingSphereDatabase("sharding_db", databaseType, null, 
null, Collections.singleton(schema), new ConfigurationProperties(new 
Properties()));
+        List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setSchema(inputSchemaName).setTable("t_order").build());
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(database, schemaTables);
+        Map<String, Set<String>> expectedResult = 
Collections.singletonMap(expectedSchemaName, Collections.singleton("t_order"));
+        assertThat(actualResult, is(expectedResult));
+    }
+    
+    private static Stream<Arguments> provideSchemaNames() {
+        return Stream.of(
+                Arguments.of("lower-case schema", "public", "public"),
+                Arguments.of("unquoted upper-case schema", "PUBLIC", "public"),
+                Arguments.of("quoted upper-case schema", "\"PUBLIC\"", 
"PUBLIC"),
+                Arguments.of("quoted mixed-case schema", "\"CaseSchema\"", 
"CaseSchema"));
+    }
+    
+    @Test
+    void 
assertParseTableExpressionsDistinguishesQuotedAndUnquotedSchemaNames() {
+        ShardingSphereSchema unquotedSchema = mockSchema("caseschema", 
"t_unquoted");
+        ShardingSphereSchema quotedSchema = mockSchema("CaseSchema", 
"t_quoted");
+        ShardingSphereDatabase database = new ShardingSphereDatabase(
+                "sharding_db", databaseType, null, null, 
Arrays.asList(unquotedSchema, quotedSchema), new ConfigurationProperties(new 
Properties()));
+        List<SchemaTable> schemaTables = Arrays.asList(
+                
SchemaTable.newBuilder().setSchema("CaseSchema").setTable("t_unquoted").build(),
+                
SchemaTable.newBuilder().setSchema("\"CaseSchema\"").setTable("t_quoted").build());
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(database, schemaTables);
+        Map<String, Set<String>> expectedResult = new HashMap<>(2, 1F);
+        expectedResult.put("caseschema", Collections.singleton("t_unquoted"));
+        expectedResult.put("CaseSchema", Collections.singleton("t_quoted"));
         assertThat(actualResult, is(expectedResult));
     }
     
     @Test
-    void assertParseTableExpressionFillDefaultSchema() {
+    void assertParseTableExpressionsFillDefaultSchema() {
         ShardingSphereSchema publicSchema = mockSchema("public", "t_order");
         ShardingSphereDatabase database = new 
ShardingSphereDatabase("sharding_db", databaseType, null, null, 
Collections.singleton(publicSchema), new ConfigurationProperties(new 
Properties()));
         List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setTable("t_order").build());
-        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressionWithSchema(database, schemaTables);
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(database, schemaTables);
         Map<String, Set<String>> expectedResult = 
Collections.singletonMap("public", Collections.singleton("t_order"));
         assertThat(actualResult, is(expectedResult));
     }
     
     @Test
-    void assertParseTableExpressionWithMissingTable() {
+    void assertParseTableExpressionsWithMissingTable() {
         ShardingSphereSchema publicSchema = mockSchema("public", "t_exist");
         ShardingSphereDatabase database = new 
ShardingSphereDatabase("sharding_db", databaseType, null, null, 
Collections.singleton(publicSchema), new ConfigurationProperties(new 
Properties()));
         List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setSchema("public").setTable("t_missing").build());
-        assertThrows(TableNotFoundException.class, () -> 
CDCSchemaTableUtils.parseTableExpressionWithSchema(database, schemaTables));
+        assertThrows(TableNotFoundException.class, () -> 
CDCSchemaTableUtils.parseTableExpressions(database, schemaTables));
     }
     
     @Test
-    void assertParseTableExpressionWithoutSchema() {
+    void assertParseTableExpressionsWithoutSchemaWithWildcard() {
         ShardingSphereSchema publicSchema = mockSchema("public", "t_order", 
"t_order2");
         ShardingSphereDatabase database = new ShardingSphereDatabase("public", 
TypedSPILoader.getService(DatabaseType.class, "FIXTURE"), null, null, 
Collections.singleton(publicSchema),
                 new ConfigurationProperties(new Properties()));
-        List<String> tableNames = Collections.singletonList("*");
-        Collection<String> actualWildcardTable = 
CDCSchemaTableUtils.parseTableExpressionWithoutSchema(database, tableNames);
-        Set<String> expectedWildcardTable = new 
HashSet<>(Arrays.asList("t_order", "t_order2"));
-        assertThat(actualWildcardTable, is(expectedWildcardTable));
+        List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setTable("*").build());
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(database, schemaTables);
+        Map<String, Set<String>> expectedResult = Collections.singletonMap("", 
new HashSet<>(Arrays.asList("t_order", "t_order2")));
+        assertThat(actualResult, is(expectedResult));
+    }
+    
+    @Test
+    void assertParseTableExpressionsWithoutSchemaWithExplicitTable() {
         ShardingSphereDatabase databaseWithoutSchema =
                 new ShardingSphereDatabase("missing", 
TypedSPILoader.getService(DatabaseType.class, "FIXTURE"), null, null, 
Collections.emptyList(), new ConfigurationProperties(new Properties()));
-        List<String> singleTable = Collections.singletonList("t_order");
-        Collection<String> actualTableNames = 
CDCSchemaTableUtils.parseTableExpressionWithoutSchema(databaseWithoutSchema, 
singleTable);
-        Set<String> expectedTableNames = new HashSet<>(singleTable);
-        assertThat(actualTableNames, is(expectedTableNames));
+        List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setSchema("ignored").setTable("t_order").build());
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(databaseWithoutSchema, schemaTables);
+        Map<String, Set<String>> expectedResult = Collections.singletonMap("", 
Collections.singleton("t_order"));
+        assertThat(actualResult, is(expectedResult));
+    }
+    
+    @Test
+    void assertParseTableExpressionsWithoutSchemaWithUnmatchedWildcard() {
+        ShardingSphereDatabase databaseWithoutSchema =
+                new ShardingSphereDatabase("missing", 
TypedSPILoader.getService(DatabaseType.class, "FIXTURE"), null, null, 
Collections.emptyList(), new ConfigurationProperties(new Properties()));
+        List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setTable("*").build());
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(databaseWithoutSchema, schemaTables);
+        assertThat(actualResult, is(Collections.emptyMap()));
+    }
+    
+    @Test
+    void assertParseTableExpressionsWithoutSchemaWithEmptyMetadata() {
+        ShardingSphereSchema schema = mockSchema("foo_db");
+        ShardingSphereDatabase database = new ShardingSphereDatabase("foo_db", 
TypedSPILoader.getService(DatabaseType.class, "FIXTURE"), null, null, 
Collections.singleton(schema),
+                new ConfigurationProperties(new Properties()));
+        List<SchemaTable> schemaTables = 
Collections.singletonList(SchemaTable.newBuilder().setTable("*").build());
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(database, schemaTables);
+        assertThat(actualResult, is(Collections.emptyMap()));
+    }
+    
+    @Test
+    void assertParseTableExpressionsWithEmptyExpressions() {
+        ShardingSphereDatabase database =
+                new ShardingSphereDatabase("foo_db", 
TypedSPILoader.getService(DatabaseType.class, "FIXTURE"), null, null, 
Collections.emptyList(), new ConfigurationProperties(new Properties()));
+        Map<String, Set<String>> actualResult = 
CDCSchemaTableUtils.parseTableExpressions(database, Collections.emptyList());
+        assertThat(actualResult, is(Collections.emptyMap()));
     }
     
     private ShardingSphereSchema mockSchema(final String schemaName, final 
String... tableNames) {
diff --git 
a/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/api/MigrationJobAPI.java
 
b/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/api/MigrationJobAPI.java
index c28194c3a7c..32cc5055c00 100644
--- 
a/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/api/MigrationJobAPI.java
+++ 
b/kernel/data-pipeline/scenario/migration/core/src/main/java/org/apache/shardingsphere/data/pipeline/scenario/migration/api/MigrationJobAPI.java
@@ -57,6 +57,7 @@ import 
org.apache.shardingsphere.infra.exception.kernel.metadata.resource.storag
 import 
org.apache.shardingsphere.infra.exception.kernel.metadata.rule.EmptyRuleException;
 import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import 
org.apache.shardingsphere.infra.metadata.database.resource.unit.StorageUnit;
+import 
org.apache.shardingsphere.infra.metadata.identifier.ShardingSphereIdentifier;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.apache.shardingsphere.infra.util.json.JsonEngine;
 import org.apache.shardingsphere.infra.yaml.config.pojo.YamlRootConfiguration;
@@ -82,6 +83,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -112,11 +114,23 @@ public final class MigrationJobAPI implements 
TransmissionJobAPI {
      * @return job ID
      */
     public String schedule(final PipelineContextKey contextKey, final 
Collection<MigrationSourceTargetEntry> sourceTargetEntries, final String 
targetDatabaseName) {
-        MigrationJobConfiguration jobConfig = new 
YamlMigrationJobConfigurationSwapper().swapToObject(buildYamlJobConfiguration(contextKey,
 sourceTargetEntries, targetDatabaseName));
+        Collection<MigrationSourceTargetEntry> distinctSourceTargetEntries = 
new HashSet<>(sourceTargetEntries);
+        checkSourceTableNames(distinctSourceTargetEntries);
+        MigrationJobConfiguration jobConfig = new 
YamlMigrationJobConfigurationSwapper().swapToObject(buildYamlJobConfiguration(contextKey,
 distinctSourceTargetEntries, targetDatabaseName));
         jobManager.start(jobConfig);
         return jobConfig.getJobId();
     }
     
+    private void checkSourceTableNames(final 
Collection<MigrationSourceTargetEntry> sourceTargetEntries) {
+        Map<String, Set<ShardingSphereIdentifier>> actualTableNames = new 
HashMap<>(sourceTargetEntries.size(), 1F);
+        for (MigrationSourceTargetEntry each : sourceTargetEntries) {
+            String dataSourceName = each.getSource().getDataSourceName();
+            Set<ShardingSphereIdentifier> tableNames = 
actualTableNames.computeIfAbsent(dataSourceName, key -> new HashSet<>());
+            ShardingSpherePreconditions.checkState(tableNames.add(new 
ShardingSphereIdentifier(each.getSource().getTableName())),
+                    () -> new PipelineInvalidParameterException("More than one 
source table with the same table name for " + dataSourceName));
+        }
+    }
+    
     private YamlMigrationJobConfiguration buildYamlJobConfiguration(final 
PipelineContextKey contextKey,
                                                                     final 
Collection<MigrationSourceTargetEntry> sourceTargetEntries, final String 
targetDatabaseName) {
         YamlMigrationJobConfiguration result = new 
YamlMigrationJobConfiguration();
@@ -125,7 +139,7 @@ public final class MigrationJobAPI implements 
TransmissionJobAPI {
         Map<String, List<DataNode>> sourceDataNodes = new 
LinkedHashMap<>(sourceTargetEntries.size(), 1F);
         Map<String, YamlPipelineDataSourceConfiguration> configSources = new 
LinkedHashMap<>(sourceTargetEntries.size(), 1F);
         YamlDataSourceConfigurationSwapper dataSourceConfigSwapper = new 
YamlDataSourceConfigurationSwapper();
-        for (MigrationSourceTargetEntry each : new 
HashSet<>(sourceTargetEntries).stream()
+        for (MigrationSourceTargetEntry each : sourceTargetEntries.stream()
                 
.sorted(Comparator.comparing(MigrationSourceTargetEntry::getTargetTableName).thenComparing(each
 -> each.getSource().format())).collect(Collectors.toList())) {
             sourceDataNodes.computeIfAbsent(each.getTargetTableName(), key -> 
new LinkedList<>()).add(each.getSource());
             ShardingSpherePreconditions.checkState(1 == 
sourceDataNodes.get(each.getTargetTableName()).size(),
diff --git 
a/kernel/data-pipeline/scenario/migration/core/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/api/MigrationJobAPITest.java
 
b/kernel/data-pipeline/scenario/migration/core/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/api/MigrationJobAPITest.java
new file mode 100644
index 00000000000..380b123f7f0
--- /dev/null
+++ 
b/kernel/data-pipeline/scenario/migration/core/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/api/MigrationJobAPITest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.api;
+
+import org.apache.shardingsphere.data.pipeline.core.context.PipelineContextKey;
+import 
org.apache.shardingsphere.data.pipeline.core.exception.param.PipelineInvalidParameterException;
+import 
org.apache.shardingsphere.data.pipeline.core.job.service.PipelineJobManager;
+import 
org.apache.shardingsphere.data.pipeline.core.metadata.PipelineDataSourcePersistService;
+import org.apache.shardingsphere.infra.datanode.DataNode;
+import org.apache.shardingsphere.infra.instance.metadata.InstanceType;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.internal.configuration.plugins.Plugins;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class MigrationJobAPITest {
+    
+    private final PipelineContextKey contextKey = new 
PipelineContextKey(InstanceType.PROXY);
+    
+    @Mock
+    private PipelineJobManager jobManager;
+    
+    @Mock
+    private PipelineDataSourcePersistService dataSourcePersistService;
+    
+    private MigrationJobAPI jobAPI;
+    
+    @BeforeEach
+    void setUp() throws ReflectiveOperationException {
+        jobAPI = new MigrationJobAPI();
+        
Plugins.getMemberAccessor().set(MigrationJobAPI.class.getDeclaredField("jobManager"),
 jobAPI, jobManager);
+        
Plugins.getMemberAccessor().set(MigrationJobAPI.class.getDeclaredField("dataSourcePersistService"),
 jobAPI, dataSourcePersistService);
+    }
+    
+    @Test
+    void assertScheduleThrowsForConflictingSourceTableIdentity() {
+        Collection<MigrationSourceTargetEntry> entries = Arrays.asList(
+                new MigrationSourceTargetEntry(new DataNode("foo_ds", 
"foo_schema", "foo_tbl"), "foo_target"),
+                new MigrationSourceTargetEntry(new DataNode("foo_ds", 
"bar_schema", "FOO_TBL"), "bar_target"));
+        PipelineInvalidParameterException actual = 
assertThrows(PipelineInvalidParameterException.class, () -> 
jobAPI.schedule(contextKey, entries, "foo_db"));
+        assertThat(actual.getMessage(), is("There is invalid parameter value. 
More than one source table with the same table name for foo_ds"));
+        verify(dataSourcePersistService, 
never()).load(any(PipelineContextKey.class), anyString());
+        verify(jobManager, never()).start(any());
+    }
+    
+    @Test
+    void assertScheduleAcceptsExactDuplicateSourceTargetEntry() {
+        MigrationSourceTargetEntry entry = new MigrationSourceTargetEntry(new 
DataNode("foo_ds", "foo_schema", "foo_tbl"), "foo_target");
+        IllegalStateException expected = new IllegalStateException("reached 
configuration");
+        when(dataSourcePersistService.load(contextKey, 
"MIGRATION")).thenThrow(expected);
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class,
+                () -> jobAPI.schedule(contextKey, Arrays.asList(entry, entry), 
"foo_db"));
+        assertThat(actual, is(expected));
+        verify(dataSourcePersistService).load(contextKey, "MIGRATION");
+        verify(jobManager, never()).start(any());
+    }
+    
+    @Test
+    void assertScheduleAcceptsSameTableNameFromDifferentDataSources() {
+        Collection<MigrationSourceTargetEntry> entries = Arrays.asList(
+                new MigrationSourceTargetEntry(new DataNode("foo_ds", 
"foo_schema", "foo_tbl"), "foo_target"),
+                new MigrationSourceTargetEntry(new DataNode("bar_ds", 
"bar_schema", "FOO_TBL"), "bar_target"));
+        IllegalStateException expected = new IllegalStateException("reached 
configuration");
+        when(dataSourcePersistService.load(contextKey, 
"MIGRATION")).thenThrow(expected);
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class, () -> jobAPI.schedule(contextKey, 
entries, "foo_db"));
+        assertThat(actual, is(expected));
+        verify(dataSourcePersistService).load(contextKey, "MIGRATION");
+        verify(jobManager, never()).start(any());
+    }
+}
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 1800b3f528a..c78dd42c3a3 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
@@ -28,6 +28,8 @@ import 
org.apache.shardingsphere.data.pipeline.scenario.migration.api.MigrationJ
 import 
org.apache.shardingsphere.data.pipeline.scenario.migration.api.MigrationSourceTargetEntry;
 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.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.distsql.handler.aware.DistSQLExecutorDatabaseAware;
 import 
org.apache.shardingsphere.distsql.handler.engine.update.DistSQLUpdateExecutor;
@@ -68,19 +70,26 @@ 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 = null == each.getSourceSchemaName() ? 
getDefaultSchemaName(contextKey, each.getSourceDatabaseName()).orElse(null) : 
each.getSourceSchemaName();
+            String schemaName = getSchemaName(contextKey, 
each.getSourceDatabaseName(), each.getSourceSchemaName()).orElse(null);
             result.add(new MigrationSourceTargetEntry(new 
DataNode(each.getSourceDatabaseName(), schemaName, each.getSourceTableName()), 
each.getTargetTableName()));
         }
         return result;
     }
     
-    private Optional<String> getDefaultSchemaName(final PipelineContextKey 
contextKey, final String sourceDatabaseName) {
-        if (new 
DatabaseTypeRegistry(database.getProtocolType()).getDialectDatabaseMetaData().getSchemaOption().isSchemaAvailable())
 {
-            Map<String, DataSourcePoolProperties> metaDataDataSource = new 
PipelineDataSourcePersistService().load(contextKey, "MIGRATION");
-            Map<String, Object> sourceDataSourcePoolProps = new 
YamlDataSourceConfigurationSwapper().swapToMap(metaDataDataSource.get(sourceDatabaseName));
-            return Optional.of(PipelineSchemaUtils.getDefaultSchema(new 
StandardPipelineDataSourceConfiguration(sourceDataSourcePoolProps)));
+    private Optional<String> getSchemaName(final PipelineContextKey 
contextKey, final String sourceDatabaseName, final String sourceSchemaName) {
+        Map<String, DataSourcePoolProperties> metaDataDataSource = new 
PipelineDataSourcePersistService().load(contextKey, "MIGRATION");
+        DataSourcePoolProperties sourceDataSourcePoolProps = 
metaDataDataSource.get(sourceDatabaseName);
+        if (null == sourceDataSourcePoolProps) {
+            return Optional.ofNullable(sourceSchemaName);
         }
-        return Optional.empty();
+        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 Optional.ofNullable(null == sourceSchemaName
+                ? PipelineSchemaUtils.getDefaultSchema(sourceDataSourceConfig)
+                : 
IdentifierNormalizeEngine.normalize(IdentifierNormalizeEngine.resolvePolicy(sourceDataSourceConfig.getDatabaseType(),
 null, IdentifierScope.SCHEMA), sourceSchemaName));
     }
     
     @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
new file mode 100644
index 00000000000..6f4184fea51
--- /dev/null
+++ 
b/kernel/data-pipeline/scenario/migration/distsql/handler/src/test/java/org/apache/shardingsphere/data/pipeline/scenario/migration/distsql/handler/update/MigrateTableExecutorTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.handler.update;
+
+import 
org.apache.shardingsphere.data.pipeline.api.type.StandardPipelineDataSourceConfiguration;
+import org.apache.shardingsphere.data.pipeline.core.context.PipelineContextKey;
+import org.apache.shardingsphere.data.pipeline.core.job.api.TransmissionJobAPI;
+import 
org.apache.shardingsphere.data.pipeline.core.metadata.PipelineDataSourcePersistService;
+import 
org.apache.shardingsphere.data.pipeline.core.metadata.loader.PipelineSchemaUtils;
+import 
org.apache.shardingsphere.data.pipeline.scenario.migration.api.MigrationJobAPI;
+import 
org.apache.shardingsphere.data.pipeline.scenario.migration.api.MigrationSourceTargetEntry;
+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.identifier.IdentifierCasePolicy;
+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.DatabaseTypeRegistry;
+import 
org.apache.shardingsphere.infra.datasource.pool.props.domain.DataSourcePoolProperties;
+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.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class MigrateTableExecutorTest {
+    
+    private static final String SOURCE_DATABASE_NAME = "foo_source";
+    
+    private static final String TARGET_DATABASE_NAME = "foo_target";
+    
+    private final MigrateTableExecutor executor = new MigrateTableExecutor();
+    
+    @Mock
+    private ShardingSphereDatabase database;
+    
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private ContextManager contextManager;
+    
+    @Mock
+    private MigrationJobAPI jobAPI;
+    
+    @Mock
+    private DatabaseType schemaCapableDatabaseType;
+    
+    @Mock
+    private DatabaseType schemaLessDatabaseType;
+    
+    @Mock
+    private DataSourcePoolProperties sourceProps;
+    
+    @Captor
+    private ArgumentCaptor<Collection<MigrationSourceTargetEntry>> 
entriesCaptor;
+    
+    @BeforeEach
+    void setUp() {
+        executor.setDatabase(database);
+        when(database.getName()).thenReturn(TARGET_DATABASE_NAME);
+        
when(contextManager.getMetaDataContexts().getMetaData().containsDatabase(TARGET_DATABASE_NAME)).thenReturn(true);
+    }
+    
+    @Test
+    void assertExecuteUpdateUsesSourceDatabaseTypeForDefaultSchema() {
+        mockSourceProps();
+        try (
+                MockedConstruction<PipelineDataSourcePersistService> ignored = 
mockConstruction(PipelineDataSourcePersistService.class,
+                        (mock, context) -> 
when(mock.load(any(PipelineContextKey.class), 
eq("MIGRATION"))).thenReturn(Collections.singletonMap(SOURCE_DATABASE_NAME, 
sourceProps)));
+                MockedConstruction<StandardPipelineDataSourceConfiguration> 
ignoredConfig = mockConstruction(StandardPipelineDataSourceConfiguration.class,
+                        (mock, context) -> 
when(mock.getDatabaseType()).thenReturn(schemaCapableDatabaseType));
+                MockedConstruction<DatabaseTypeRegistry> ignoredRegistry = 
mockDatabaseTypeRegistries();
+                MockedStatic<TypedSPILoader> spiLoader = 
mockStatic(TypedSPILoader.class, Answers.CALLS_REAL_METHODS);
+                MockedStatic<PipelineSchemaUtils> schemaUtils = 
mockStatic(PipelineSchemaUtils.class)) {
+            spiLoader.when(() -> 
TypedSPILoader.getService(TransmissionJobAPI.class, 
"MIGRATION")).thenReturn(jobAPI);
+            schemaUtils.when(() -> 
PipelineSchemaUtils.getDefaultSchema(any(StandardPipelineDataSourceConfiguration.class))).thenReturn("CaseSchema");
+            executor.executeUpdate(createStatement(null), contextManager);
+            verify(jobAPI).schedule(any(PipelineContextKey.class), 
entriesCaptor.capture(), eq(TARGET_DATABASE_NAME));
+            
assertThat(entriesCaptor.getValue().iterator().next().getSource().getSchemaName(),
 is("CaseSchema"));
+            verify(database, never()).getProtocolType();
+        }
+    }
+    
+    @Test
+    void assertExecuteUpdateDoesNotLoadDefaultSchemaForSchemaLessSource() {
+        mockSourceProps();
+        try (
+                MockedConstruction<PipelineDataSourcePersistService> ignored = 
mockConstruction(PipelineDataSourcePersistService.class,
+                        (mock, context) -> 
when(mock.load(any(PipelineContextKey.class), 
eq("MIGRATION"))).thenReturn(Collections.singletonMap(SOURCE_DATABASE_NAME, 
sourceProps)));
+                MockedConstruction<StandardPipelineDataSourceConfiguration> 
ignoredConfig = mockConstruction(StandardPipelineDataSourceConfiguration.class,
+                        (mock, context) -> 
when(mock.getDatabaseType()).thenReturn(schemaLessDatabaseType));
+                MockedConstruction<DatabaseTypeRegistry> ignoredRegistry = 
mockDatabaseTypeRegistries();
+                MockedStatic<TypedSPILoader> spiLoader = 
mockStatic(TypedSPILoader.class, Answers.CALLS_REAL_METHODS);
+                MockedStatic<PipelineSchemaUtils> schemaUtils = 
mockStatic(PipelineSchemaUtils.class)) {
+            spiLoader.when(() -> 
TypedSPILoader.getService(TransmissionJobAPI.class, 
"MIGRATION")).thenReturn(jobAPI);
+            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());
+            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);
+        try (
+                MockedConstruction<PipelineDataSourcePersistService> ignored = 
mockConstruction(PipelineDataSourcePersistService.class,
+                        (mock, context) -> 
when(mock.load(any(PipelineContextKey.class), 
eq("MIGRATION"))).thenReturn(Collections.singletonMap(SOURCE_DATABASE_NAME, 
sourceProps)));
+                MockedConstruction<YamlDataSourceConfigurationSwapper> 
ignoredSwapper = mockConstruction(YamlDataSourceConfigurationSwapper.class,
+                        (mock, context) -> 
when(mock.swapToMap(sourceProps)).thenReturn(Collections.emptyMap()));
+                MockedConstruction<StandardPipelineDataSourceConfiguration> 
ignoredConfig = mockConstruction(StandardPipelineDataSourceConfiguration.class,
+                        (mock, context) -> 
when(mock.getDatabaseType()).thenReturn(schemaCapableDatabaseType));
+                MockedConstruction<DatabaseTypeRegistry> ignoredRegistry = 
mockDatabaseTypeRegistries();
+                MockedStatic<TypedSPILoader> spiLoader = 
mockStatic(TypedSPILoader.class, Answers.CALLS_REAL_METHODS);
+                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);
+            verify(jobAPI).schedule(any(PipelineContextKey.class), 
entriesCaptor.capture(), eq(TARGET_DATABASE_NAME));
+            
assertThat(entriesCaptor.getValue().iterator().next().getSource().getSchemaName(),
 is(expectedSchemaName));
+            schemaUtils.verify(() -> 
PipelineSchemaUtils.getDefaultSchema(any(StandardPipelineDataSourceConfiguration.class)),
 never());
+        }
+    }
+    
+    private static Stream<Arguments> provideExplicitSchemaNames() {
+        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"));
+    }
+    
+    @Test
+    void assertExecuteUpdateDefersMissingSourceValidation() {
+        try (
+                MockedConstruction<PipelineDataSourcePersistService> ignored = 
mockConstruction(PipelineDataSourcePersistService.class,
+                        (mock, context) -> 
when(mock.load(any(PipelineContextKey.class), 
eq("MIGRATION"))).thenReturn(Collections.emptyMap()));
+                MockedStatic<TypedSPILoader> spiLoader = 
mockStatic(TypedSPILoader.class, Answers.CALLS_REAL_METHODS);
+                MockedStatic<PipelineSchemaUtils> schemaUtils = 
mockStatic(PipelineSchemaUtils.class)) {
+            spiLoader.when(() -> 
TypedSPILoader.getService(TransmissionJobAPI.class, 
"MIGRATION")).thenReturn(jobAPI);
+            executor.executeUpdate(createStatement(null), contextManager);
+            verify(jobAPI).schedule(any(PipelineContextKey.class), 
entriesCaptor.capture(), eq(TARGET_DATABASE_NAME));
+            
assertNull(entriesCaptor.getValue().iterator().next().getSource().getSchemaName());
+            schemaUtils.verify(() -> 
PipelineSchemaUtils.getDefaultSchema(any(StandardPipelineDataSourceConfiguration.class)),
 never());
+            verify(database, never()).getProtocolType();
+        }
+    }
+    
+    private MigrateTableStatement createStatement(final String 
sourceSchemaName) {
+        MigrationSourceTargetSegment entry = new 
MigrationSourceTargetSegment(SOURCE_DATABASE_NAME, sourceSchemaName, "foo_tbl", 
"foo_tbl");
+        return new MigrateTableStatement(null, Collections.singleton(entry));
+    }
+    
+    private void mockSourceProps() {
+        
when(sourceProps.getAllStandardProperties()).thenReturn(Collections.emptyMap());
+        
when(sourceProps.getPoolClassName()).thenReturn("com.zaxxer.hikari.HikariDataSource");
+    }
+    
+    private MockedConstruction<DatabaseTypeRegistry> 
mockDatabaseTypeRegistries() {
+        return mockConstruction(DatabaseTypeRegistry.class, (mock, context) -> 
{
+            DialectDatabaseMetaData databaseMetaData = 
mock(DialectDatabaseMetaData.class, Answers.RETURNS_DEEP_STUBS);
+            
when(databaseMetaData.getSchemaOption().isSchemaAvailable()).thenReturn(schemaCapableDatabaseType
 == context.arguments().get(0));
+            
when(mock.getDialectDatabaseMetaData()).thenReturn(databaseMetaData);
+        });
+    }
+}
diff --git 
a/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/cases/migration/general/PostgreSQLMigrationGeneralE2EIT.java
 
b/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/cases/migration/general/PostgreSQLMigrationGeneralE2EIT.java
index 819945b8d29..1d92f7eb410 100644
--- 
a/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/cases/migration/general/PostgreSQLMigrationGeneralE2EIT.java
+++ 
b/test/e2e/operation/pipeline/src/test/java/org/apache/shardingsphere/test/e2e/operation/pipeline/cases/migration/general/PostgreSQLMigrationGeneralE2EIT.java
@@ -47,6 +47,7 @@ import java.util.concurrent.TimeUnit;
 
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @PipelineE2ESettings(database = {
@@ -57,6 +58,8 @@ class PostgreSQLMigrationGeneralE2EIT extends 
AbstractMigrationE2EIT {
     
     private static final String SOURCE_TABLE_NAME = "t_order";
     
+    private static final long CROSS_SCHEMA_SENTINEL_ORDER_ID = 20000L;
+    
     @ParameterizedTest(name = "{0}")
     @EnabledIf("isEnabled")
     @ArgumentsSource(PipelineE2ETestCaseArgumentsProvider.class)
@@ -68,6 +71,8 @@ class PostgreSQLMigrationGeneralE2EIT extends 
AbstractMigrationE2EIT {
             IntPkLargeOrderDAO orderDAO = new 
IntPkLargeOrderDAO(containerComposer.getSourceDataSource(),
                     containerComposer.getDatabaseType(), 
containerComposer.createQualifiedTableWithSchema(SOURCE_TABLE_NAME).format());
             orderDAO.createTable();
+            IntPkLargeOrderDAO publicOrderDAO = new 
IntPkLargeOrderDAO(containerComposer.getSourceDataSource(), 
containerComposer.getDatabaseType(), "public." + SOURCE_TABLE_NAME);
+            publicOrderDAO.createTable();
             IntPkOrderItemDAO orderItemDAO = new 
IntPkOrderItemDAO(containerComposer.getSourceDataSource(), 
containerComposer.getDatabaseType(), PipelineContainerComposer.SCHEMA_NAME);
             orderItemDAO.createTable();
             
containerComposer.createSourceTableIndexList(PipelineContainerComposer.SCHEMA_NAME,
 SOURCE_TABLE_NAME);
@@ -85,6 +90,8 @@ class PostgreSQLMigrationGeneralE2EIT extends 
AbstractMigrationE2EIT {
             Awaitility.waitAtMost(10L, TimeUnit.SECONDS).pollInterval(1L, 
TimeUnit.SECONDS).until(() -> !distSQLFacade.listJobIds().isEmpty());
             String jobId = distSQLFacade.getJobIdByTableName("ds_0.test." + 
SOURCE_TABLE_NAME);
             distSQLFacade.waitJobPreparingStageFinished(jobId);
+            distSQLFacade.waitJobIncrementalStageStarted(jobId);
+            publicOrderDAO.insert(CROSS_SCHEMA_SENTINEL_ORDER_ID, 1, "OK");
             String qualifiedTableName = String.join(".", 
PipelineContainerComposer.SCHEMA_NAME, SOURCE_TABLE_NAME);
             containerComposer.startIncrementTask(new 
E2EIncrementalTask(containerComposer.getSourceDataSource(), qualifiedTableName, 
new SnowflakeKeyGenerateAlgorithm(),
                     containerComposer.getDatabaseType(), 20));
@@ -93,6 +100,7 @@ class PostgreSQLMigrationGeneralE2EIT extends 
AbstractMigrationE2EIT {
             // TODO Insert new record in t_order_item
             DataSource jdbcDataSource = 
containerComposer.generateShardingSphereDataSourceFromProxy();
             containerComposer.assertRecordExists(jdbcDataSource, 
qualifiedTableName, 10000);
+            assertRecordAbsent(jdbcDataSource, qualifiedTableName, 
CROSS_SCHEMA_SENTINEL_ORDER_ID);
             checkOrderMigration(distSQLFacade, jobId);
             startMigrationWithSchema(containerComposer, "t_order_item", 
"t_order_item");
             checkOrderItemMigration(distSQLFacade);
@@ -106,6 +114,15 @@ class PostgreSQLMigrationGeneralE2EIT extends 
AbstractMigrationE2EIT {
         }
     }
     
+    private void assertRecordAbsent(final DataSource dataSource, final String 
tableName, 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", 
tableName, orderId))) {
+            assertFalse(resultSet.next(), "Cross-schema sentinel must not be 
migrated");
+        }
+    }
+    
     private void checkOrderMigration(final PipelineE2EDistSQLFacade 
distSQLFacade, final String jobId) throws SQLException {
         distSQLFacade.waitJobIncrementalStageFinished(jobId);
         distSQLFacade.pauseJob(jobId);

Reply via email to