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

github-merge-queue[bot] pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 7c4f2d7720 [Fix][Connector-V2] Apply Postgres-CDC ADD COLUMN on the 
first RELATION with pgjdbc 42.7.5+ (#12547)
7c4f2d7720 is described below

commit 7c4f2d77209fd0c8edcc42aff105625f3d2a1406
Author: Goutam Adwant <[email protected]>
AuthorDate: Fri Oct 9 09:26:13 2026 +0000

    [Fix][Connector-V2] Apply Postgres-CDC ADD COLUMN on the first RELATION 
with pgjdbc 42.7.5+ (#12547)
---
 .../postgresql/RelationAwarePostgresSchema.java    |  20 ++-
 .../RelationAwarePostgresSchemaTest.java           | 141 +++++++++++++++++++++
 2 files changed, 160 insertions(+), 1 deletion(-)

diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/io/debezium/connector/postgresql/RelationAwarePostgresSchema.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/io/debezium/connector/postgresql/RelationAwarePostgresSchema.java
index 6300dd8d6b..fef54bf08f 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/io/debezium/connector/postgresql/RelationAwarePostgresSchema.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/io/debezium/connector/postgresql/RelationAwarePostgresSchema.java
@@ -30,6 +30,7 @@ import java.util.function.Consumer;
 /** A PostgreSQL schema that exposes pgoutput RELATION changes to SeaTunnel. */
 public class RelationAwarePostgresSchema extends PostgresSchema {
 
+    private final String databaseName;
     private Consumer<Table> relationChangeListener;
 
     public RelationAwarePostgresSchema(
@@ -39,6 +40,7 @@ public class RelationAwarePostgresSchema extends 
PostgresSchema {
             TopicSelector<TableId> topicSelector,
             PostgresValueConverter valueConverter) {
         super(config, typeRegistry, defaultValueConverter, topicSelector, 
valueConverter);
+        this.databaseName = config.databaseName();
     }
 
     public void setRelationChangeListener(Consumer<Table> 
relationChangeListener) {
@@ -51,7 +53,7 @@ public class RelationAwarePostgresSchema extends 
PostgresSchema {
      */
     @Override
     public void applySchemaChangesForTable(int relationId, Table table) {
-        Table previousTable = tableFor(table.id());
+        Table previousTable = trackedTableFor(table.id());
         super.applySchemaChangesForTable(relationId, table);
 
         // Always forward streaming RELATION messages for known tables. After 
recovery Debezium may
@@ -63,6 +65,22 @@ public class RelationAwarePostgresSchema extends 
PostgresSchema {
         }
     }
 
+    /**
+     * pgoutput RELATION ids carry no catalog, but pgjdbc 42.7.5+ returns the 
database name as
+     * TABLE_CAT, so tables loaded from JDBC metadata may be tracked as 
database.schema.table.
+     */
+    private Table trackedTableFor(TableId relationTableId) {
+        Table table = tableFor(relationTableId);
+        if (table != null
+                || relationTableId.catalog() != null
+                || databaseName == null
+                || databaseName.isEmpty()) {
+            return table;
+        }
+        return tableFor(
+                new TableId(databaseName, relationTableId.schema(), 
relationTableId.table()));
+    }
+
     /** Compare two raw pgoutput RELATION schemas, including their primary-key 
definitions. */
     public static boolean hasSameRelationSchema(Table before, Table after) {
         if (!Objects.equals(before.primaryKeyColumnNames(), 
after.primaryKeyColumnNames())) {
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/test/java/io/debezium/connector/postgresql/RelationAwarePostgresSchemaTest.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/test/java/io/debezium/connector/postgresql/RelationAwarePostgresSchemaTest.java
new file mode 100644
index 0000000000..d44bd13818
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/test/java/io/debezium/connector/postgresql/RelationAwarePostgresSchemaTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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 io.debezium.connector.postgresql;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import io.debezium.config.Configuration;
+import io.debezium.connector.postgresql.connection.PostgresConnection;
+import 
io.debezium.connector.postgresql.connection.PostgresDefaultValueConverter;
+import io.debezium.relational.Column;
+import io.debezium.relational.Table;
+import io.debezium.relational.TableId;
+import io.debezium.relational.Tables;
+import io.debezium.schema.TopicSelector;
+
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+public class RelationAwarePostgresSchemaTest {
+
+    private static final String DATABASE = "traffic";
+
+    @Test
+    public void 
testRelationNotifiesListenerWhenSchemaWasLoadedWithoutCatalog() throws 
Exception {
+        // pgjdbc before 42.7.5 returns TABLE_CAT = NULL, so Debezium keys the 
table without it.
+        List<Table> notified =
+                applyFirstRelation(new TableId(null, "public", "users"), 
relationWithNewColumn());
+
+        Assertions.assertEquals(1, notified.size());
+        Assertions.assertEquals(2, notified.get(0).columns().size());
+    }
+
+    @Test
+    public void testRelationNotifiesListenerWhenSchemaWasLoadedWithCatalog() 
throws Exception {
+        // pgjdbc 42.7.5+ returns TABLE_CAT = <database>, while pgoutput 
RELATION ids have none.
+        List<Table> notified =
+                applyFirstRelation(
+                        new TableId(DATABASE, "public", "users"), 
relationWithNewColumn());
+
+        Assertions.assertEquals(1, notified.size());
+        Assertions.assertEquals(2, notified.get(0).columns().size());
+    }
+
+    @Test
+    public void testRelationForUnknownTableDoesNotNotifyListener() throws 
Exception {
+        List<Table> notified =
+                applyFirstRelation(
+                        new TableId(DATABASE, "public", "other"), 
relationWithNewColumn());
+
+        Assertions.assertTrue(notified.isEmpty());
+    }
+
+    @Test
+    public void testRelationDoesNotMatchTableFromAnotherCatalog() throws 
Exception {
+        List<Table> notified =
+                applyFirstRelation(
+                        new TableId("other_db", "public", "users"), 
relationWithNewColumn());
+
+        Assertions.assertTrue(notified.isEmpty());
+    }
+
+    private static List<Table> applyFirstRelation(TableId loadedTableId, Table 
relation)
+            throws Exception {
+        RelationAwarePostgresSchema schema = newSchema();
+        PostgresConnection connection = mock(PostgresConnection.class);
+        doAnswer(
+                        invocation -> {
+                            Tables tables = invocation.getArgument(0);
+                            tables.overwriteTable(table(loadedTableId, "id"));
+                            return null;
+                        })
+                .when(connection)
+                .readSchema(any(), any(), any(), any(), any(), anyBoolean());
+        schema.refresh(connection, false);
+
+        List<Table> notified = new ArrayList<>();
+        schema.setRelationChangeListener(notified::add);
+        schema.applySchemaChangesForTable(1, relation);
+        return notified;
+    }
+
+    private static RelationAwarePostgresSchema newSchema() {
+        PostgresConnectorConfig config =
+                new PostgresConnectorConfig(
+                        Configuration.create()
+                                .with("database.server.name", "seatunnel")
+                                .with("database.dbname", DATABASE)
+                                .build());
+        return new RelationAwarePostgresSchema(
+                config,
+                mock(TypeRegistry.class),
+                mock(PostgresDefaultValueConverter.class),
+                TopicSelector.defaultSelector(config, (id, prefix, delimiter) 
-> id.toString()),
+                mock(PostgresValueConverter.class));
+    }
+
+    private static Table relationWithNewColumn() {
+        return table(new TableId(null, "public", "users"), "id", "extra");
+    }
+
+    private static Table table(TableId tableId, String... columnNames) {
+        List<Column> columns = new ArrayList<>();
+        for (int i = 0; i < columnNames.length; i++) {
+            columns.add(
+                    Column.editor()
+                            .name(columnNames[i])
+                            .type("int4")
+                            .jdbcType(Types.INTEGER)
+                            .position(i + 1)
+                            .optional(i != 0)
+                            .create());
+        }
+        return Table.editor()
+                .tableId(tableId)
+                .addColumns(columns)
+                .setPrimaryKeyNames(columnNames[0])
+                .create();
+    }
+}

Reply via email to