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

loserwang1024 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 306cb329c6 [FLINK-40453][postgres] Not load table schema when 
initializing record emitter (#4513)
306cb329c6 is described below

commit 306cb329c6de54a7f42b6a0ff326a7ded8cb50c2
Author: naivedogger <[email protected]>
AuthorDate: Tue Aug 25 10:21:44 2026 +0800

    [FLINK-40453][postgres] Not load table schema when initializing record 
emitter (#4513)
---
 .../reader/PostgresPipelineRecordEmitter.java      | 150 +++++++++------------
 .../reader/PostgresPipelineRecordEmitterTest.java  | 103 ++++++++++++++
 2 files changed, 166 insertions(+), 87 deletions(-)

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/reader/PostgresPipelineRecordEmitter.java
 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/source/reader/PostgresPipelineRecordEmitter.java
index 1030263e84..6df94c28b0 100644
--- 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/source/reader/PostgresPipelineRecordEmitter.java
+++ 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/main/java/org/apache/flink/cdc/connectors/postgres/source/reader/PostgresPipelineRecordEmitter.java
@@ -21,16 +21,12 @@ import org.apache.flink.api.connector.source.SourceOutput;
 import org.apache.flink.cdc.common.event.CreateTableEvent;
 import org.apache.flink.cdc.common.event.SchemaChangeEvent;
 import org.apache.flink.cdc.common.schema.Schema;
-import org.apache.flink.cdc.connectors.base.options.StartupOptions;
 import org.apache.flink.cdc.connectors.base.source.meta.offset.OffsetFactory;
-import org.apache.flink.cdc.connectors.base.source.meta.split.SnapshotSplit;
-import org.apache.flink.cdc.connectors.base.source.meta.split.SourceSplitBase;
 import org.apache.flink.cdc.connectors.base.source.meta.split.SourceSplitState;
 import org.apache.flink.cdc.connectors.base.source.metrics.SourceReaderMetrics;
 import org.apache.flink.cdc.connectors.postgres.source.PostgresDialect;
 import 
org.apache.flink.cdc.connectors.postgres.source.config.PostgresSourceConfig;
 import 
org.apache.flink.cdc.connectors.postgres.source.schema.PostgresSchemaRecord;
-import 
org.apache.flink.cdc.connectors.postgres.source.utils.TableDiscoveryUtils;
 import org.apache.flink.cdc.connectors.postgres.utils.PostgresSchemaUtils;
 import org.apache.flink.cdc.debezium.DebeziumDeserializationSchema;
 import org.apache.flink.cdc.debezium.event.DebeziumEventDeserializationSchema;
@@ -41,14 +37,13 @@ import io.debezium.data.Envelope;
 import io.debezium.relational.Table;
 import io.debezium.relational.TableId;
 import io.debezium.relational.history.TableChanges;
+import io.debezium.relational.history.TableChanges.TableChange;
 import org.apache.kafka.connect.data.Field;
 import org.apache.kafka.connect.data.Struct;
 import org.apache.kafka.connect.source.SourceRecord;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.sql.SQLException;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -60,7 +55,6 @@ import static 
org.apache.flink.cdc.connectors.base.source.meta.wartermark.Waterm
 import static 
org.apache.flink.cdc.connectors.base.utils.SourceRecordUtils.isSchemaChangeEvent;
 import static 
org.apache.flink.cdc.connectors.postgres.utils.PostgresSchemaUtils.toCdcTableId;
 import static 
org.apache.flink.cdc.connectors.postgres.utils.SchemaChangeUtil.inferSchemaChangeEvent;
-import static 
org.apache.flink.cdc.connectors.postgres.utils.SchemaChangeUtil.toCreateTableEvent;
 
 /** The {@link RecordEmitter} implementation for PostgreSQL pipeline 
connector. */
 public class PostgresPipelineRecordEmitter<T> extends 
PostgresSourceRecordEmitter<T> {
@@ -69,13 +63,9 @@ public class PostgresPipelineRecordEmitter<T> extends 
PostgresSourceRecordEmitte
 
     // Used when startup mode is initial
     private final Set<TableId> alreadySendCreateTableTables;
-    private final boolean isBounded;
     private final boolean includeDatabaseInTableId;
     private final Map<TableId, CreateTableEvent> createTableEventCache;
 
-    // Used when startup mode is not initial
-    private boolean shouldEmitAllCreateTableEventsInSnapshotMode = true;
-
     public PostgresPipelineRecordEmitter(
             DebeziumDeserializationSchema<T> debeziumDeserializationSchema,
             SourceReaderMetrics sourceReaderMetrics,
@@ -94,61 +84,27 @@ public class PostgresPipelineRecordEmitter<T> extends 
PostgresSourceRecordEmitte
         this.createTableEventCache =
                 ((DebeziumEventDeserializationSchema) 
debeziumDeserializationSchema)
                         .getCreateTableEventCache();
-        generateCreateTableEvent(sourceConfig);
-        this.isBounded = 
StartupOptions.snapshot().equals(sourceConfig.getStartupOptions());
-    }
-
-    @Override
-    public void applySplit(SourceSplitBase split) {
-        if ((isBounded) && createTableEventCache.isEmpty() && split instanceof 
SnapshotSplit) {
-            // TableSchemas in SnapshotSplit only contains one table.
-            
createTableEventCache.putAll(generateCreateTableEvent(sourceConfig));
-        } else {
-            for (Map.Entry<TableId, TableChanges.TableChange> entry :
-                    split.getTableSchemas().entrySet()) {
-                TableChanges.TableChange tableChange = entry.getValue();
-
-                Table table = tableChange.getTable();
-                CreateTableEvent createTableEvent =
-                        toCreateTableEvent(table, sourceConfig, 
postgresDialect);
-                ((DebeziumEventDeserializationSchema) 
debeziumDeserializationSchema)
-                        .applyChangeEvent(createTableEvent);
-            }
-        }
     }
 
     @Override
     protected void processElement(
             SourceRecord element, SourceOutput<T> output, SourceSplitState 
splitState)
             throws Exception {
-        if (shouldEmitAllCreateTableEventsInSnapshotMode && isBounded) {
-            // In snapshot mode, we simply emit all schemas at once.
-            createTableEventCache.forEach(
-                    (tableId, createTableEvent) -> {
-                        output.collect((T) createTableEvent);
-                    });
-            shouldEmitAllCreateTableEventsInSnapshotMode = false;
-        } else if (isLowWatermarkEvent(element) && 
splitState.isSnapshotSplitState()) {
+        if (isLowWatermarkEvent(element) && splitState.isSnapshotSplitState()) 
{
             TableId tableId = 
splitState.asSnapshotSplitState().toSourceSplit().getTableId();
-            maybeSendCreateTableEventFromCache(tableId, output);
+            sendCreateTableEventIfNeeded(tableId, output, splitState);
         } else if (isDataChangeRecord(element)) {
-            handleDataChangeRecord(element, output);
+            handleDataChangeRecord(element, output, splitState);
         } else if (isSchemaChangeEvent(element) && 
sourceConfig.isIncludeSchemaChanges()) {
             handleSchemaChangeRecord(element, output, splitState);
         }
         super.processElement(element, output, splitState);
     }
 
-    private void handleDataChangeRecord(SourceRecord element, SourceOutput<T> 
output) {
+    private void handleDataChangeRecord(
+            SourceRecord element, SourceOutput<T> output, SourceSplitState 
splitState) {
         TableId tableId = getTableId(element);
-        maybeSendCreateTableEventFromCache(tableId, output);
-        // In rare case, we may miss some CreateTableEvents before 
DataChangeEvents.
-        // Don't send CreateTableEvent for SchemaChangeEvents as it's the 
latest schema.
-        if (!createTableEventCache.containsKey(tableId)) {
-            CreateTableEvent createTableEvent = 
getCreateTableEvent(sourceConfig, tableId);
-            sendCreateTableEvent(createTableEvent, output);
-            createTableEventCache.put(tableId, createTableEvent);
-        }
+        sendCreateTableEventIfNeeded(tableId, output, splitState);
     }
 
     private void handleSchemaChangeRecord(
@@ -163,32 +119,79 @@ public class PostgresPipelineRecordEmitter<T> extends 
PostgresSourceRecordEmitte
                 splitState.toSourceSplit().getTableSchemas();
         PostgresSchemaRecord schemaRecord = (PostgresSchemaRecord) element;
         Table schemaAfter = schemaRecord.getTable();
-        maybeSendCreateTableEventFromCache(schemaAfter.id(), output);
         Table schemaBefore = null;
         if (existedTableSchemas.containsKey(schemaAfter.id())) {
             schemaBefore = 
existedTableSchemas.get(schemaAfter.id()).getTable();
         }
+        if (schemaBefore != null) {
+            sendCreateTableEventIfNeeded(schemaAfter.id(), output, splitState);
+        }
         List<SchemaChangeEvent> schemaChangeEvents =
                 inferSchemaChangeEvent(
                         schemaAfter.id(), schemaBefore, schemaAfter, 
sourceConfig, postgresDialect);
         LOG.info("Inferred Schema change events: {}", schemaChangeEvents);
-        schemaChangeEvents.forEach(schemaChangeEvent -> output.collect((T) 
schemaChangeEvent));
+        schemaChangeEvents.forEach(
+                schemaChangeEvent -> {
+                    output.collect((T) schemaChangeEvent);
+                    if (schemaChangeEvent instanceof CreateTableEvent) {
+                        cacheCreateTableEvent(
+                                schemaAfter.id(), (CreateTableEvent) 
schemaChangeEvent);
+                        alreadySendCreateTableTables.add(schemaAfter.id());
+                    }
+                });
     }
 
-    private void maybeSendCreateTableEventFromCache(TableId tableId, 
SourceOutput<T> output) {
-        if (!alreadySendCreateTableTables.contains(tableId)) {
-            CreateTableEvent createTableEvent = 
createTableEventCache.get(tableId);
-            if (createTableEvent != null) {
-                sendCreateTableEvent(createTableEvent, output);
-            }
-            alreadySendCreateTableTables.add(tableId);
+    private void sendCreateTableEventIfNeeded(
+            TableId tableId, SourceOutput<T> output, SourceSplitState 
splitState) {
+        if (alreadySendCreateTableTables.contains(tableId)) {
+            return;
         }
+
+        CreateTableEvent createTableEvent = 
getOrCreateCreateTableEvent(tableId, splitState);
+        sendCreateTableEvent(createTableEvent, output);
+        alreadySendCreateTableTables.add(tableId);
     }
 
     private void sendCreateTableEvent(CreateTableEvent createTableEvent, 
SourceOutput<T> output) {
         output.collect((T) createTableEvent);
     }
 
+    private CreateTableEvent getOrCreateCreateTableEvent(
+            TableId tableId, SourceSplitState splitState) {
+        CreateTableEvent createTableEvent = createTableEventCache.get(tableId);
+        if (createTableEvent == null) {
+            createTableEvent = getCreateTableEventFromSplit(tableId, 
splitState);
+        }
+        if (createTableEvent == null) {
+            createTableEvent = getCreateTableEvent(sourceConfig, tableId);
+        }
+        cacheCreateTableEvent(tableId, createTableEvent);
+        return createTableEvent;
+    }
+
+    private CreateTableEvent getCreateTableEventFromSplit(
+            TableId tableId, SourceSplitState splitState) {
+        TableChange tableChange = 
splitState.toSourceSplit().getTableSchemas().get(tableId);
+        if (tableChange == null || tableChange.getTable() == null) {
+            return null;
+        }
+        try (PostgresConnection jdbc = postgresDialect.openJdbcConnection()) {
+            return new CreateTableEvent(
+                    toCdcTableId(
+                            tableId,
+                            sourceConfig.getDatabaseList().get(0),
+                            includeDatabaseInTableId),
+                    PostgresSchemaUtils.toSchema(
+                            tableChange.getTable(),
+                            sourceConfig.getDbzConnectorConfig(),
+                            jdbc.getTypeRegistry()));
+        }
+    }
+
+    private void cacheCreateTableEvent(TableId tableId, CreateTableEvent 
createTableEvent) {
+        createTableEventCache.put(tableId, createTableEvent);
+    }
+
     private CreateTableEvent getCreateTableEvent(
             PostgresSourceConfig sourceConfig, TableId tableId) {
         try (PostgresConnection jdbc = postgresDialect.openJdbcConnection()) {
@@ -213,31 +216,4 @@ public class PostgresPipelineRecordEmitter<T> extends 
PostgresSourceRecordEmitte
         String tableName = source.getString(TABLE_NAME_KEY);
         return new TableId(null, schemaName, tableName);
     }
-
-    private Map<TableId, CreateTableEvent> generateCreateTableEvent(
-            PostgresSourceConfig sourceConfig) {
-        try (PostgresConnection jdbc = postgresDialect.openJdbcConnection()) {
-            Map<TableId, CreateTableEvent> createTableEventCache = new 
HashMap<>();
-            List<TableId> capturedTableIds =
-                    TableDiscoveryUtils.listTables(
-                            sourceConfig.getDatabaseList().get(0),
-                            jdbc,
-                            sourceConfig.getTableFilters(),
-                            sourceConfig.includePartitionedTables());
-            for (TableId tableId : capturedTableIds) {
-                Schema schema = PostgresSchemaUtils.getTableSchema(tableId, 
sourceConfig, jdbc);
-                createTableEventCache.put(
-                        tableId,
-                        new CreateTableEvent(
-                                toCdcTableId(
-                                        tableId,
-                                        
this.sourceConfig.getDatabaseList().get(0),
-                                        includeDatabaseInTableId),
-                                schema));
-            }
-            return createTableEventCache;
-        } catch (SQLException e) {
-            throw new RuntimeException("Cannot start emitter to fetch table 
schema.", e);
-        }
-    }
 }
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/reader/PostgresPipelineRecordEmitterTest.java
 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-postgres/src/test/java/org/apache/flink/cdc/connectors/postgres/source/reader/PostgresPipelineRecordEmitterTest.java
new file mode 100644
index 0000000000..8d54cba23b
--- /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/reader/PostgresPipelineRecordEmitterTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.reader;
+
+import org.apache.flink.cdc.common.event.Event;
+import org.apache.flink.cdc.connectors.base.options.StartupOptions;
+import org.apache.flink.cdc.connectors.base.source.meta.split.SnapshotSplit;
+import org.apache.flink.cdc.connectors.postgres.source.PostgresDialect;
+import 
org.apache.flink.cdc.connectors.postgres.source.PostgresEventDeserializer;
+import 
org.apache.flink.cdc.connectors.postgres.source.config.PostgresSourceConfig;
+import 
org.apache.flink.cdc.connectors.postgres.source.config.PostgresSourceConfigFactory;
+import 
org.apache.flink.cdc.connectors.postgres.source.offset.PostgresOffsetFactory;
+import org.apache.flink.cdc.debezium.table.DebeziumChangelogMode;
+import org.apache.flink.table.types.logical.IntType;
+import org.apache.flink.table.types.logical.RowType;
+
+import io.debezium.connector.postgresql.connection.PostgresConnection;
+import io.debezium.relational.TableId;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+/** Tests for {@link PostgresPipelineRecordEmitter}. */
+class PostgresPipelineRecordEmitterTest {
+
+    private static final TableId DBZ_TABLE_ID = new TableId(null, "public", 
"users");
+
+    @Test
+    void testInitializationDoesNotLoadTableSchemas() {
+        PostgresSourceConfig sourceConfig = 
createSourceConfig(StartupOptions.initial());
+
+        assertThatCode(() -> 
createEmitter(sourceConfig)).doesNotThrowAnyException();
+    }
+
+    @Test
+    void testSnapshotSplitAssignmentDoesNotLoadAllTableSchemas() {
+        PostgresSourceConfig sourceConfig = 
createSourceConfig(StartupOptions.snapshot());
+        PostgresPipelineRecordEmitter<Event> emitter = 
createEmitter(sourceConfig);
+
+        assertThatCode(() -> 
emitter.applySplit(createSnapshotSplit())).doesNotThrowAnyException();
+    }
+
+    private static PostgresPipelineRecordEmitter<Event> createEmitter(
+            PostgresSourceConfig sourceConfig) {
+        return new PostgresPipelineRecordEmitter<>(
+                new PostgresEventDeserializer(DebeziumChangelogMode.ALL),
+                null,
+                sourceConfig,
+                new PostgresOffsetFactory(),
+                new FailingPostgresDialect(sourceConfig));
+    }
+
+    private static PostgresSourceConfig createSourceConfig(StartupOptions 
startupOptions) {
+        PostgresSourceConfigFactory configFactory = new 
PostgresSourceConfigFactory();
+        configFactory.hostname("localhost");
+        configFactory.port(5432);
+        configFactory.username("user");
+        configFactory.password("password");
+        configFactory.database("db0");
+        configFactory.tableList("public.users");
+        configFactory.startupOptions(startupOptions);
+        return configFactory.create(0);
+    }
+
+    private static SnapshotSplit createSnapshotSplit() {
+        return new SnapshotSplit(
+                DBZ_TABLE_ID,
+                "public.users:0",
+                RowType.of(new IntType()),
+                null,
+                null,
+                null,
+                Collections.emptyMap());
+    }
+
+    private static class FailingPostgresDialect extends PostgresDialect {
+        private FailingPostgresDialect(PostgresSourceConfig sourceConfig) {
+            super(sourceConfig);
+        }
+
+        @Override
+        public PostgresConnection openJdbcConnection() {
+            throw new AssertionError("JDBC connection should be opened 
lazily.");
+        }
+    }
+}

Reply via email to