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

lvyanquan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink-cdc.git


The following commit(s) were added to refs/heads/master by this push:
     new 5b5d1dbe3 [FLINK-34806][postgres] Expose 
scan.newly-added-table.enabled option (#4436)
5b5d1dbe3 is described below

commit 5b5d1dbe3e32b815502f6e07ca42f9a6159bfe0e
Author: Mehmet Can Şakiroğlu <[email protected]>
AuthorDate: Wed Sep 23 12:37:17 2026 +0300

    [FLINK-34806][postgres] Expose scan.newly-added-table.enabled option (#4436)
    
    Signed-off-by: Mehmet Can Şakiroğlu <[email protected]>
    Co-authored-by: lvyanquan <[email protected]>
---
 .../connectors/pipeline-connectors/postgres.md     |  12 +
 .../connectors/pipeline-connectors/postgres.md     |  13 +
 .../factory/PostgresDataSourceFactory.java         |   4 +
 .../postgres/source/PostgresDataSourceOptions.java |  12 +
 .../factory/PostgresDataSourceFactoryTest.java     |  20 ++
 .../PostgresPipelineNewlyAddedTableITCase.java     | 400 +++++++++++++++++++++
 .../postgres/testutils/UniqueDatabase.java         |  38 +-
 7 files changed, 483 insertions(+), 16 deletions(-)

diff --git a/docs/content.zh/docs/connectors/pipeline-connectors/postgres.md 
b/docs/content.zh/docs/connectors/pipeline-connectors/postgres.md
index 02fb272cf..4a231d4ec 100644
--- a/docs/content.zh/docs/connectors/pipeline-connectors/postgres.md
+++ b/docs/content.zh/docs/connectors/pipeline-connectors/postgres.md
@@ -283,6 +283,18 @@ pipeline:
         默认值为 false。
       </td>
     </tr>
+    <tr>
+      <td>scan.newly-added-table.enabled</td>
+      <td>optional</td>
+      <td style="word-wrap: break-word;">false</td>
+      <td>Boolean</td>
+      <td>
+        是否扫描新增的表。默认值为 false。
+        该选项仅在从 savepoint 或 checkpoint 恢复时生效,
+        会启用 SnapshotSplitAssigner#captureNewlyAddedTables() 代码路径,
+        以发现在 savepoint 时刻未被捕获但匹配 source <code>tables</code> 模式的新表。
+      </td>
+    </tr>
     <tr>
       <td>schema-change.enabled</td>
       <td>optional</td>
diff --git a/docs/content/docs/connectors/pipeline-connectors/postgres.md 
b/docs/content/docs/connectors/pipeline-connectors/postgres.md
index 1deb246f5..174261258 100644
--- a/docs/content/docs/connectors/pipeline-connectors/postgres.md
+++ b/docs/content/docs/connectors/pipeline-connectors/postgres.md
@@ -275,6 +275,19 @@ pipeline:
         Defaults to false.
       </td>
     </tr>
+    <tr>
+      <td>scan.newly-added-table.enabled</td>
+      <td>optional</td>
+      <td style="word-wrap: break-word;">false</td>
+      <td>Boolean</td>
+      <td>
+        Whether to scan the newly added tables or not. Defaults to false.
+        This option only takes effect when restoring from a savepoint or 
checkpoint,
+        and enables the existing 
SnapshotSplitAssigner#captureNewlyAddedTables() code path
+        to discover tables that match the source <code>tables</code> pattern 
but were not part of
+        the captured set at savepoint time.
+      </td>
+    </tr>
     <tr>
       <td>schema-change.enabled</td>
       <td>optional</td>
diff --git 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/factory/PostgresDataSourceFactory.java
 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/factory/PostgresDataSourceFactory.java
index bb6332f01..5a7f5f932 100644
--- 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/factory/PostgresDataSourceFactory.java
+++ 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/factory/PostgresDataSourceFactory.java
@@ -72,6 +72,7 @@ import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSource
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SCAN_INCREMENTAL_SNAPSHOT_CHUNK_SIZE;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SCAN_INCREMENTAL_SNAPSHOT_UNBOUNDED_CHUNK_FIRST_ENABLED;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SCAN_LSN_COMMIT_CHECKPOINTS_DELAY;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SCAN_NEWLY_ADDED_TABLE_ENABLED;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SCAN_SNAPSHOT_FETCH_SIZE;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SCAN_STARTUP_MODE;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SCHEMA_CHANGE_ENABLED;
@@ -133,6 +134,7 @@ public class PostgresDataSourceFactory implements 
DataSourceFactory {
         int lsnCommitCheckpointsDelay = 
config.get(SCAN_LSN_COMMIT_CHECKPOINTS_DELAY);
         boolean tableIdIncludeDatabase = config.get(TABLE_ID_INCLUDE_DATABASE);
         boolean includeSchemaChanges = config.get(SCHEMA_CHANGE_ENABLED);
+        boolean scanNewlyAddedTableEnabled = 
config.get(SCAN_NEWLY_ADDED_TABLE_ENABLED);
 
         validateIntegerOption(SCAN_INCREMENTAL_SNAPSHOT_CHUNK_SIZE, splitSize, 
1);
         validateIntegerOption(CHUNK_META_GROUP_SIZE, splitMetaGroupSize, 1);
@@ -175,6 +177,7 @@ public class PostgresDataSourceFactory implements 
DataSourceFactory {
                         .assignUnboundedChunkFirst(isAssignUnboundedChunkFirst)
                         .includeDatabaseInTableId(tableIdIncludeDatabase)
                         .includeSchemaChanges(includeSchemaChanges)
+                        .scanNewlyAddedTableEnabled(scanNewlyAddedTableEnabled)
                         .getConfigFactory();
 
         List<TableId> tableIds = 
PostgresSchemaUtils.listTables(configFactory.create(0), null);
@@ -266,6 +269,7 @@ public class PostgresDataSourceFactory implements 
DataSourceFactory {
         options.add(SCAN_INCREMENTAL_SNAPSHOT_UNBOUNDED_CHUNK_FIRST_ENABLED);
         options.add(TABLE_ID_INCLUDE_DATABASE);
         options.add(SCHEMA_CHANGE_ENABLED);
+        options.add(SCAN_NEWLY_ADDED_TABLE_ENABLED);
         return options;
     }
 
diff --git 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/source/PostgresDataSourceOptions.java
 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/source/PostgresDataSourceOptions.java
index 95cc823d2..183d7cc89 100644
--- 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/source/PostgresDataSourceOptions.java
+++ 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/source/PostgresDataSourceOptions.java
@@ -281,4 +281,16 @@ public class PostgresDataSourceOptions {
                     .defaultValue(false)
                     .withDescription(
                             "Whether to infer CDC column types when processing 
pgoutput Relation messages.");
+
+    public static final ConfigOption<Boolean> SCAN_NEWLY_ADDED_TABLE_ENABLED =
+            ConfigOptions.key("scan.newly-added-table.enabled")
+                    .booleanType()
+                    .defaultValue(false)
+                    .withDescription(
+                            "Whether to scan the newly added tables or not. 
Defaults to false. "
+                                    + "This option only takes effect when 
restoring from a savepoint or checkpoint, "
+                                    + "and enables the existing 
SnapshotSplitAssigner#captureNewlyAddedTables() code path "
+                                    + "to discover tables that match the 
source `tables:` pattern but were not part of "
+                                    + "the captured set at savepoint time. 
Mirrors the MySQL Pipeline connector option "
+                                    + "of the same name.");
 }
diff --git 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/test/java/org/apache/flink/cdc/connectors/postgres/factory/PostgresDataSourceFactoryTest.java
 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/test/java/org/apache/flink/cdc/connectors/postgres/factory/PostgresDataSourceFactoryTest.java
index c3cce43bc..f64a0935b 100644
--- 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/test/java/org/apache/flink/cdc/connectors/postgres/factory/PostgresDataSourceFactoryTest.java
+++ 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/test/java/org/apache/flink/cdc/connectors/postgres/factory/PostgresDataSourceFactoryTest.java
@@ -48,6 +48,7 @@ import java.util.stream.Collectors;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.HOSTNAME;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.PASSWORD;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.PG_PORT;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SCAN_NEWLY_ADDED_TABLE_ENABLED;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SLOT_NAME;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.TABLES;
 import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.TABLES_EXCLUDE;
@@ -97,6 +98,25 @@ public class PostgresDataSourceFactoryTest extends 
PostgresTestBase {
                 .isEqualTo(Arrays.asList("inventory.products"));
     }
 
+    @Test
+    public void testScanNewlyAddedTableEnabled() {
+        Map<String, String> options = new HashMap<>();
+        options.put(HOSTNAME.key(), POSTGRES_CONTAINER.getHost());
+        options.put(
+                PG_PORT.key(), 
String.valueOf(POSTGRES_CONTAINER.getMappedPort(POSTGRESQL_PORT)));
+        options.put(USERNAME.key(), TEST_USER);
+        options.put(PASSWORD.key(), TEST_PASSWORD);
+        options.put(TABLES.key(), POSTGRES_CONTAINER.getDatabaseName() + 
".inventory.prod\\.*");
+        options.put(SLOT_NAME.key(), slotName);
+        options.put(SCAN_NEWLY_ADDED_TABLE_ENABLED.key(), "true");
+
+        Factory.Context context = new 
MockContext(Configuration.fromMap(options));
+        PostgresDataSourceFactory factory = new PostgresDataSourceFactory();
+        PostgresDataSource dataSource = (PostgresDataSource) 
factory.createDataSource(context);
+
+        
assertThat(dataSource.getPostgresSourceConfig().isScanNewlyAddedTableEnabled()).isTrue();
+    }
+
     @Test
     public void testNoMatchedTable() {
         Map<String, String> options = new HashMap<>();
diff --git 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/test/java/org/apache/flink/cdc/connectors/postgres/source/PostgresPipelineNewlyAddedTableITCase.java
 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/test/java/org/apache/flink/cdc/connectors/postgres/source/PostgresPipelineNewlyAddedTableITCase.java
new file mode 100644
index 000000000..20a1c57a8
--- /dev/null
+++ 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/test/java/org/apache/flink/cdc/connectors/postgres/source/PostgresPipelineNewlyAddedTableITCase.java
@@ -0,0 +1,400 @@
+/*
+ * 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.flink.cdc.connectors.postgres.source;
+
+import org.apache.flink.api.common.eventtime.WatermarkStrategy;
+import org.apache.flink.api.common.typeutils.TypeSerializer;
+import org.apache.flink.cdc.common.data.binary.BinaryStringData;
+import org.apache.flink.cdc.common.event.CreateTableEvent;
+import org.apache.flink.cdc.common.event.DataChangeEvent;
+import org.apache.flink.cdc.common.event.Event;
+import org.apache.flink.cdc.common.event.TableId;
+import org.apache.flink.cdc.common.factories.Factory;
+import org.apache.flink.cdc.common.factories.FactoryHelper;
+import org.apache.flink.cdc.common.schema.Schema;
+import org.apache.flink.cdc.common.source.FlinkSourceProvider;
+import org.apache.flink.cdc.common.types.DataType;
+import org.apache.flink.cdc.common.types.DataTypes;
+import org.apache.flink.cdc.common.types.RowType;
+import org.apache.flink.cdc.connectors.postgres.PostgresTestBase;
+import 
org.apache.flink.cdc.connectors.postgres.factory.PostgresDataSourceFactory;
+import org.apache.flink.cdc.connectors.postgres.testutils.UniqueDatabase;
+import org.apache.flink.cdc.runtime.typeutils.BinaryRecordDataGenerator;
+import org.apache.flink.cdc.runtime.typeutils.EventTypeInfo;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.StateRecoveryOptions;
+import org.apache.flink.core.execution.JobClient;
+import org.apache.flink.core.execution.SavepointFormatType;
+import org.apache.flink.streaming.api.datastream.DataStreamSource;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import 
org.apache.flink.streaming.api.operators.collect.AbstractCollectResultBuffer;
+import 
org.apache.flink.streaming.api.operators.collect.CheckpointedCollectResultBuffer;
+import org.apache.flink.streaming.api.operators.collect.CollectResultIterator;
+import 
org.apache.flink.streaming.api.operators.collect.CollectResultIteratorAdapter;
+import org.apache.flink.streaming.api.operators.collect.CollectSinkOperator;
+import 
org.apache.flink.streaming.api.operators.collect.CollectSinkOperatorFactory;
+import org.apache.flink.streaming.api.operators.collect.CollectStreamSink;
+import org.apache.flink.streaming.util.RestartStrategyUtils;
+
+import io.debezium.connector.postgresql.connection.PostgresConnection;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.DECODING_PLUGIN_NAME;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.HOSTNAME;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.PASSWORD;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.PG_PORT;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SCAN_NEWLY_ADDED_TABLE_ENABLED;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SERVER_TIME_ZONE;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.SLOT_NAME;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.TABLES;
+import static 
org.apache.flink.cdc.connectors.postgres.source.PostgresDataSourceOptions.USERNAME;
+import static org.assertj.core.api.Assertions.assertThat;
+import static 
org.testcontainers.containers.PostgreSQLContainer.POSTGRESQL_PORT;
+
+/** IT tests to cover newly added tables during capture process in Postgres 
pipeline mode. */
+class PostgresPipelineNewlyAddedTableITCase extends PostgresTestBase {
+
+    private static final String SCHEMA_NAME = "customer";
+
+    private final UniqueDatabase customDatabase =
+            new UniqueDatabase(
+                    POSTGRES_CONTAINER, "postgres", SCHEMA_NAME, TEST_USER, 
TEST_PASSWORD);
+
+    private String slotName;
+
+    @BeforeEach
+    void before() throws SQLException {
+        customDatabase.createAndInitialize();
+        this.slotName = getSlotName();
+    }
+
+    @AfterEach
+    void after() throws Exception {
+        // sleep 1000ms to wait until connections are closed.
+        Thread.sleep(1000L);
+        customDatabase.removeSlot(slotName);
+    }
+
+    private PostgresConnection getConnection() {
+        Map<String, String> properties = new HashMap<>();
+        properties.put("hostname", customDatabase.getHost());
+        properties.put("port", 
String.valueOf(customDatabase.getDatabasePort()));
+        properties.put("user", customDatabase.getUsername());
+        properties.put("password", customDatabase.getPassword());
+        properties.put("dbname", customDatabase.getDatabaseName());
+        return createConnection(properties);
+    }
+
+    @Test
+    void testAddNewTableByPatternSingleParallelism() throws Exception {
+        // step 1: create the first table
+        initialAddressTables(getConnection(), 
Collections.singletonList("address_hangzhou"));
+
+        Path savepointDir = Files.createTempDirectory("add-new-table-test");
+        final String savepointDirectory = 
savepointDir.toAbsolutePath().toString();
+        String finishedSavePointPath = null;
+
+        // step 2: listen to the pattern in the first round with single 
parallelism
+        // so that each table emits exactly one CreateTableEvent.
+        StreamExecutionEnvironment env = 
getStreamExecutionEnvironment(finishedSavePointPath, 1);
+        List<String> tables = Collections.singletonList("address_\\.*");
+        FlinkSourceProvider sourceProvider = getFlinkSourceProvider(tables, 1);
+        DataStreamSource<Event> source =
+                env.fromSource(
+                        sourceProvider.getSource(),
+                        WatermarkStrategy.noWatermarks(),
+                        PostgresDataSourceFactory.IDENTIFIER,
+                        new EventTypeInfo());
+
+        TypeSerializer<Event> serializer =
+                source.getTransformation()
+                        .getOutputType()
+                        
.createSerializer(env.getConfig().getSerializerConfig());
+        CheckpointedCollectResultBuffer<Event> resultBuffer =
+                new CheckpointedCollectResultBuffer<>(serializer);
+        String accumulatorName = "dataStreamCollect_" + UUID.randomUUID();
+        CollectResultIterator<Event> iterator =
+                addCollector(env, source, resultBuffer, serializer, 
accumulatorName);
+        JobClient jobClient = env.executeAsync("beforeAddNewTable");
+        iterator.setJobClient(jobClient);
+
+        // step 3: assert fetched snapshot data of the first table
+        List<Event> actual = fetchResults(iterator, 4);
+        multiAssert(actual, Collections.singletonList("address_hangzhou"));
+
+        // step 4: create a newly added table matching the pattern
+        initialAddressTables(getConnection(), 
Collections.singletonList("address_beijing"));
+
+        // sleep 1s to wait for the assign status to 
INITIAL_ASSIGNING_FINISHED.
+        // Otherwise, the restart job won't read newly added tables, and this 
test will be stuck.
+        Thread.sleep(1000L);
+
+        // step 5: trigger a savepoint and cancel the job
+        finishedSavePointPath = triggerSavepointWithRetry(jobClient, 
savepointDirectory);
+        jobClient.cancel().get();
+        iterator.close();
+
+        // step 6: restore from savepoint with the same pattern
+        StreamExecutionEnvironment restoredEnv =
+                getStreamExecutionEnvironment(finishedSavePointPath, 1);
+        FlinkSourceProvider restoredSourceProvider = 
getFlinkSourceProvider(tables, 1);
+        DataStreamSource<Event> restoreSource =
+                restoredEnv.fromSource(
+                        restoredSourceProvider.getSource(),
+                        WatermarkStrategy.noWatermarks(),
+                        PostgresDataSourceFactory.IDENTIFIER,
+                        new EventTypeInfo());
+        CollectResultIterator<Event> restoredIterator =
+                addCollector(restoredEnv, restoreSource, resultBuffer, 
serializer, accumulatorName);
+        restoredEnv.executeAsync("AfterAddNewTable");
+
+        // step 7: assert the newly added table is captured after restore
+        List<Event> newlyTableEvent = fetchResults(restoredIterator, 4);
+        multiAssert(newlyTableEvent, 
Collections.singletonList("address_beijing"));
+    }
+
+    private void multiAssert(List<Event> actualEvents, List<String> 
listenTables) {
+        List<Event> expectedCreateTableEvents = new ArrayList<>();
+        List<Event> expectedDataChangeEvents = new ArrayList<>();
+        for (String table : listenTables) {
+            
expectedCreateTableEvents.add(getCreateTableEvent(TableId.tableId(SCHEMA_NAME, 
table)));
+            expectedDataChangeEvents.addAll(
+                    getSnapshotExpected(TableId.tableId(SCHEMA_NAME, table)));
+        }
+
+        // compare create table events
+        List<Event> actualCreateTableEvents =
+                actualEvents.stream()
+                        .filter(event -> event instanceof CreateTableEvent)
+                        .collect(Collectors.toList());
+        assertThat(actualCreateTableEvents)
+                
.containsExactlyInAnyOrder(expectedCreateTableEvents.toArray(new Event[0]));
+
+        // compare data change events
+        List<Event> actualDataChangeEvents =
+                actualEvents.stream()
+                        .filter(event -> event instanceof DataChangeEvent)
+                        .collect(Collectors.toList());
+        assertThat(actualDataChangeEvents)
+                
.containsExactlyInAnyOrder(expectedDataChangeEvents.toArray(new Event[0]));
+    }
+
+    private CreateTableEvent getCreateTableEvent(TableId tableId) {
+        Schema schema =
+                Schema.newBuilder()
+                        .physicalColumn("id", DataTypes.BIGINT().notNull())
+                        .physicalColumn("country", 
DataTypes.VARCHAR(255).notNull())
+                        .physicalColumn("city", 
DataTypes.VARCHAR(255).notNull())
+                        .physicalColumn("detail_address", 
DataTypes.VARCHAR(1024))
+                        .primaryKey(Collections.singletonList("id"))
+                        .build();
+        return new CreateTableEvent(tableId, schema);
+    }
+
+    private List<Event> getSnapshotExpected(TableId tableId) {
+        RowType rowType =
+                RowType.of(
+                        new DataType[] {
+                            DataTypes.BIGINT().notNull(),
+                            DataTypes.VARCHAR(255).notNull(),
+                            DataTypes.VARCHAR(255).notNull(),
+                            DataTypes.VARCHAR(1024)
+                        },
+                        new String[] {"id", "country", "city", 
"detail_address"});
+        BinaryRecordDataGenerator generator = new 
BinaryRecordDataGenerator(rowType);
+        String cityName = tableId.getTableName().split("_")[1];
+        return Arrays.asList(
+                DataChangeEvent.insertEvent(
+                        tableId,
+                        generator.generate(
+                                new Object[] {
+                                    416874195632735147L,
+                                    BinaryStringData.fromString("China"),
+                                    BinaryStringData.fromString(cityName),
+                                    BinaryStringData.fromString(cityName + " 
West Town address 1")
+                                })),
+                DataChangeEvent.insertEvent(
+                        tableId,
+                        generator.generate(
+                                new Object[] {
+                                    416927583791428523L,
+                                    BinaryStringData.fromString("China"),
+                                    BinaryStringData.fromString(cityName),
+                                    BinaryStringData.fromString(cityName + " 
West Town address 2")
+                                })),
+                DataChangeEvent.insertEvent(
+                        tableId,
+                        generator.generate(
+                                new Object[] {
+                                    417022095255614379L,
+                                    BinaryStringData.fromString("China"),
+                                    BinaryStringData.fromString(cityName),
+                                    BinaryStringData.fromString(cityName + " 
West Town address 3")
+                                })));
+    }
+
+    private String triggerSavepointWithRetry(JobClient jobClient, String 
savepointDirectory)
+            throws Exception {
+        int retryCount = 0;
+        final int maxRetries = 600;
+        while (retryCount < maxRetries) {
+            try {
+                return jobClient
+                        .triggerSavepoint(savepointDirectory, 
SavepointFormatType.DEFAULT)
+                        .get();
+            } catch (Exception e) {
+                retryCount++;
+                if (retryCount >= maxRetries) {
+                    throw e;
+                }
+                Thread.sleep(100);
+            }
+        }
+        throw new Exception("Failed to trigger savepoint after " + maxRetries 
+ " retries");
+    }
+
+    private void initialAddressTables(PostgresConnection connection, 
List<String> addressTables)
+            throws SQLException {
+        try {
+            connection.setAutoCommit(false);
+            for (String tableName : addressTables) {
+                String tableId =
+                        customDatabase.getDatabaseName() + '.' + SCHEMA_NAME + 
'.' + tableName;
+                String cityName = tableName.split("_")[1];
+                connection.execute(
+                        "CREATE TABLE "
+                                + tableId
+                                + "("
+                                + "  id BIGINT NOT NULL PRIMARY KEY,"
+                                + "  country VARCHAR(255) NOT NULL,"
+                                + "  city VARCHAR(255) NOT NULL,"
+                                + "  detail_address VARCHAR(1024)"
+                                + ");");
+                connection.execute(
+                        format(
+                                "INSERT INTO  %s "
+                                        + "VALUES (416874195632735147, 
'China', '%s', '%s West Town address 1'),"
+                                        + "       (416927583791428523, 
'China', '%s', '%s West Town address 2'),"
+                                        + "       (417022095255614379, 
'China', '%s', '%s West Town address 3');",
+                                tableId, cityName, cityName, cityName, 
cityName, cityName,
+                                cityName));
+                connection.execute(format("ALTER TABLE %s REPLICA IDENTITY 
FULL", tableId));
+            }
+            connection.commit();
+        } finally {
+            connection.close();
+        }
+    }
+
+    private FlinkSourceProvider getFlinkSourceProvider(List<String> tables, 
int parallelism) {
+        List<String> fullTableNames =
+                tables.stream()
+                        .map(
+                                table ->
+                                        customDatabase.getDatabaseName()
+                                                + "."
+                                                + SCHEMA_NAME
+                                                + "."
+                                                + table)
+                        .collect(Collectors.toList());
+        Map<String, String> options = new HashMap<>();
+        options.put(HOSTNAME.key(), POSTGRES_CONTAINER.getHost());
+        options.put(
+                PG_PORT.key(), 
String.valueOf(POSTGRES_CONTAINER.getMappedPort(POSTGRESQL_PORT)));
+        options.put(USERNAME.key(), TEST_USER);
+        options.put(PASSWORD.key(), TEST_PASSWORD);
+        options.put(SERVER_TIME_ZONE.key(), "UTC");
+        options.put(TABLES.key(), String.join(",", fullTableNames));
+        options.put(SLOT_NAME.key(), slotName);
+        options.put(DECODING_PLUGIN_NAME.key(), "pgoutput");
+        options.put(SCAN_NEWLY_ADDED_TABLE_ENABLED.key(), "true");
+
+        Factory.Context context =
+                new FactoryHelper.DefaultContext(
+                        
org.apache.flink.cdc.common.configuration.Configuration.fromMap(options),
+                        null,
+                        this.getClass().getClassLoader());
+
+        PostgresDataSourceFactory factory = new PostgresDataSourceFactory();
+        PostgresDataSource dataSource = (PostgresDataSource) 
factory.createDataSource(context);
+
+        return (FlinkSourceProvider) dataSource.getEventSourceProvider();
+    }
+
+    private <T> CollectResultIterator<T> addCollector(
+            StreamExecutionEnvironment env,
+            DataStreamSource<T> source,
+            AbstractCollectResultBuffer<T> buffer,
+            TypeSerializer<T> serializer,
+            String accumulatorName) {
+        CollectSinkOperatorFactory<T> sinkFactory =
+                new CollectSinkOperatorFactory<>(serializer, accumulatorName);
+        CollectStreamSink<T> sink = new CollectStreamSink<>(source, 
sinkFactory);
+        String operatorUid = "Data stream collect sink";
+        sink.name(operatorUid).uid(operatorUid);
+        CollectSinkOperator<T> operator = (CollectSinkOperator<T>) 
sinkFactory.getOperator();
+        env.addOperator(sink.getTransformation());
+        CollectResultIterator<T> iterator =
+                new CollectResultIteratorAdapter<>(
+                        buffer, operatorUid, operator, accumulatorName, 0);
+        env.registerCollectIterator(iterator);
+        return iterator;
+    }
+
+    private StreamExecutionEnvironment getStreamExecutionEnvironment(
+            String finishedSavePointPath, int parallelism) {
+        Configuration configuration = new Configuration();
+        if (finishedSavePointPath != null) {
+            configuration.set(StateRecoveryOptions.SAVEPOINT_PATH, 
finishedSavePointPath);
+        }
+        StreamExecutionEnvironment env =
+                
StreamExecutionEnvironment.getExecutionEnvironment(configuration);
+        env.setParallelism(parallelism);
+        env.enableCheckpointing(500L);
+        RestartStrategyUtils.configureFixedDelayRestartStrategy(env, 3, 1000L);
+        return env;
+    }
+
+    private List<Event> fetchResults(CollectResultIterator<Event> iterator, 
int size)
+            throws Exception {
+        List<Event> result = new ArrayList<>(size);
+        while (size > 0 && iterator.hasNext()) {
+            Event event = iterator.next();
+            if (event instanceof CreateTableEvent || event instanceof 
DataChangeEvent) {
+                result.add(event);
+                size--;
+            }
+        }
+        return result;
+    }
+}
diff --git 
a/flink-cdc-connect/flink-cdc-source-connectors/flink-connector-postgres-cdc/src/test/java/org/apache/flink/cdc/connectors/postgres/testutils/UniqueDatabase.java
 
b/flink-cdc-connect/flink-cdc-source-connectors/flink-connector-postgres-cdc/src/test/java/org/apache/flink/cdc/connectors/postgres/testutils/UniqueDatabase.java
index 142b71298..fb46b6d70 100644
--- 
a/flink-cdc-connect/flink-cdc-source-connectors/flink-connector-postgres-cdc/src/test/java/org/apache/flink/cdc/connectors/postgres/testutils/UniqueDatabase.java
+++ 
b/flink-cdc-connect/flink-cdc-source-connectors/flink-connector-postgres-cdc/src/test/java/org/apache/flink/cdc/connectors/postgres/testutils/UniqueDatabase.java
@@ -22,9 +22,11 @@ import 
org.apache.flink.cdc.connectors.postgres.PostgresTestBase;
 import org.assertj.core.api.Assertions;
 import org.testcontainers.containers.PostgreSQLContainer;
 
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.nio.charset.StandardCharsets;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -129,21 +131,25 @@ public class UniqueDatabase {
             createDatabase(databaseName);
             try (Connection connection =
                             PostgresTestBase.getJdbcConnection(container, 
databaseName);
-                    Statement statement = connection.createStatement()) {
+                    Statement statement = connection.createStatement();
+                    InputStream inputStream =
+                            
UniqueDatabase.class.getClassLoader().getResourceAsStream(ddlFile);
+                    BufferedReader reader =
+                            new BufferedReader(
+                                    new InputStreamReader(inputStream, 
StandardCharsets.UTF_8))) {
+                final String ddl =
+                        reader.lines()
+                                .map(String::trim)
+                                .filter(x -> !x.startsWith("--") && 
!x.isEmpty())
+                                .map(
+                                        x -> {
+                                            final Matcher m = 
COMMENT_PATTERN.matcher(x);
+                                            return m.matches() ? m.group(1) : 
x;
+                                        })
+                                .map(this::convertSQL)
+                                .collect(Collectors.joining("\n"));
                 final List<String> statements =
-                        Arrays.stream(
-                                        
Files.readAllLines(Paths.get(ddlTestFile.toURI())).stream()
-                                                .map(String::trim)
-                                                .filter(x -> 
!x.startsWith("--") && !x.isEmpty())
-                                                .map(
-                                                        x -> {
-                                                            final Matcher m =
-                                                                    
COMMENT_PATTERN.matcher(x);
-                                                            return m.matches() 
? m.group(1) : x;
-                                                        })
-                                                .map(this::convertSQL)
-                                                
.collect(Collectors.joining("\n"))
-                                                .split(";"))
+                        Arrays.stream(ddl.split(";"))
                                 .map(x -> x.replace("$$", ";"))
                                 .collect(Collectors.toList());
                 for (String stmt : statements) {

Reply via email to