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 89a5598eb5 [Fix][Connector-V2] Resolve late PostgreSQL CDC type names
by search path (#12836)
89a5598eb5 is described below
commit 89a5598eb5e258f30248009572c58d0deba24d60
Author: Goutam Adwant <[email protected]>
AuthorDate: Sun Oct 18 06:11:12 2026 +0000
[Fix][Connector-V2] Resolve late PostgreSQL CDC type names by search path
(#12836)
---
docs/en/connectors/source/PostgreSQL-CDC.md | 4 ++
docs/zh/connectors/source/PostgreSQL-CDC.md | 4 ++
.../connector/postgresql/TypeRegistry.java | 7 ++-
.../seatunnel/cdc/postgres/PostgresCDCIT.java | 73 ++++++++++++++++++++++
4 files changed, 87 insertions(+), 1 deletion(-)
diff --git a/docs/en/connectors/source/PostgreSQL-CDC.md
b/docs/en/connectors/source/PostgreSQL-CDC.md
index caf02983f1..c8f1949a6e 100644
--- a/docs/en/connectors/source/PostgreSQL-CDC.md
+++ b/docs/en/connectors/source/PostgreSQL-CDC.md
@@ -27,6 +27,10 @@ Some PostgreSQL-compatible databases (for example HighGo)
return the same physic
once from `information_schema.tables`. The connector deduplicates discovered
tables by their fully
qualified `catalog.schema.table` id, so these repeated rows are ignored and
the job starts normally.
+On a type-name cache miss, an unqualified name shared by multiple schemas
resolves to the type
+visible on the source connection's `search_path`. This does not change cached
names or add source
+type namespaces to downstream `ADD COLUMN` statements.
+
## Supported DataSource Info
| Datasource | Supported versions |
Driver | Url |
Maven |
diff --git a/docs/zh/connectors/source/PostgreSQL-CDC.md
b/docs/zh/connectors/source/PostgreSQL-CDC.md
index 438b9ac78e..d5de69eaab 100644
--- a/docs/zh/connectors/source/PostgreSQL-CDC.md
+++ b/docs/zh/connectors/source/PostgreSQL-CDC.md
@@ -25,6 +25,10 @@ PostgreSQL CDC 连接器允许从 PostgreSQL 数据库读取快照数据和增
部分 PostgreSQL 兼容数据库(例如 HighGo)在 `information_schema.tables` 中会对同一张物理表返回多行重复记录。
连接器在表发现阶段会按完整的 `catalog.schema.table` 标识去重,这些重复行会被忽略,任务可正常启动。
+当类型名未命中缓存且多个 schema 中存在同名类型时,未限定 schema 的类型名会解析为源连接的
+`search_path` 中可见的类型。此改动不影响已缓存的类型名,也不会将源类型的 schema 传递到下游的
+`ADD COLUMN` 语句。
+
## 支持的数据源信息
| 数据源 | 支持的版本 | 驱动
| Url |
Maven |
diff --git
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/io/debezium/connector/postgresql/TypeRegistry.java
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/io/debezium/connector/postgresql/TypeRegistry.java
index b4cdf55dcf..0af0e1aca2 100644
---
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/io/debezium/connector/postgresql/TypeRegistry.java
+++
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/io/debezium/connector/postgresql/TypeRegistry.java
@@ -90,7 +90,12 @@ public class TypeRegistry {
+ ") e ON (t.oid = e.id) "
+ "WHERE n.nspname != 'pg_toast' and t.typtypmod != 0";
- private static final String SQL_NAME_LOOKUP = SQL_TYPES + " AND t.typname
= ?";
+ // Match PostgreSQL's unqualified type lookup when duplicate names exist
across schemas.
+ // The OID tie-breaker keeps the fallback deterministic when no candidate
is visible.
+ private static final String SQL_NAME_LOOKUP =
+ SQL_TYPES
+ + " AND t.typname = ?"
+ + " ORDER BY pg_catalog.pg_type_is_visible(t.oid) DESC,
t.oid";
private static final String SQL_OID_LOOKUP = SQL_TYPES + " AND t.oid = ?";
diff --git
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-postgres-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/PostgresCDCIT.java
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-postgres-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/PostgresCDCIT.java
index 4123adbac8..106125c90e 100644
---
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-postgres-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/PostgresCDCIT.java
+++
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-postgres-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/PostgresCDCIT.java
@@ -59,7 +59,10 @@ import org.testcontainers.lifecycle.Startables;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.DockerLoggerFactory;
+import io.debezium.connector.postgresql.TypeRegistry;
import io.debezium.connector.postgresql.connection.Lsn;
+import io.debezium.connector.postgresql.connection.PostgresConnection;
+import io.debezium.jdbc.JdbcConfiguration;
import io.debezium.jdbc.JdbcConnection;
import io.debezium.relational.TableId;
import lombok.extern.slf4j.Slf4j;
@@ -71,6 +74,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
+import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
@@ -1274,6 +1278,75 @@ public class PostgresCDCIT extends TestSuiteBase
implements TestResource {
POSTGRES_CONTAINER.getPassword());
}
+ @Test
+ public void shouldResolveNewTypesUsingPostgresSearchPath() throws
SQLException {
+ JdbcConfiguration config =
+ JdbcConfiguration.create()
+ .withHostname(POSTGRES_CONTAINER.getHost())
+ .withPort(
+ POSTGRES_CONTAINER.getMappedPort(
+ PostgreSQLContainer.POSTGRESQL_PORT))
+ .withDatabase(POSTGRES_CONTAINER.getDatabaseName())
+ .withUser(POSTGRES_CONTAINER.getUsername())
+ .withPassword(POSTGRES_CONTAINER.getPassword())
+ .build();
+
+ try (PostgresConnection connection =
+ new PostgresConnection(config,
"type-registry-search-path-test")) {
+ // Create the types after priming so get(String) exercises
SQL_NAME_LOOKUP.
+ TypeRegistry registry = new TypeRegistry(connection);
+ try (Statement statement =
connection.connection().createStatement()) {
+ try {
+ statement.execute("CREATE SCHEMA type_lookup_a");
+ statement.execute("CREATE SCHEMA type_lookup_b");
+ statement.execute("CREATE TYPE type_lookup_b.late_mood AS
ENUM ('b')");
+ statement.execute("CREATE TYPE type_lookup_a.late_mood AS
ENUM ('a')");
+ statement.execute("SET search_path TO type_lookup_a,
public");
+ Assertions.assertEquals(
+ visibleTypeOid(connection.connection(),
"late_mood"),
+ registry.get("late_mood").getOid());
+
+ statement.execute("CREATE TYPE type_lookup_a.reverse_mood
AS ENUM ('a')");
+ statement.execute("CREATE TYPE type_lookup_b.reverse_mood
AS ENUM ('b')");
+ statement.execute("SET search_path TO type_lookup_b,
type_lookup_a, public");
+ Assertions.assertEquals(
+ visibleTypeOid(connection.connection(),
"reverse_mood"),
+ registry.get("reverse_mood").getOid());
+
+ statement.execute("CREATE TYPE type_lookup_b.hidden_mood
AS ENUM ('b')");
+ statement.execute("CREATE TYPE type_lookup_a.hidden_mood
AS ENUM ('a')");
+ statement.execute("SET search_path TO public");
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ "SELECT MIN(oid) FROM pg_catalog.pg_type
WHERE typname = 'hidden_mood'")) {
+ Assertions.assertTrue(resultSet.next());
+ // Keep the old fallback of returning a type even when
none is visible.
+ Assertions.assertEquals(
+ resultSet.getInt(1),
registry.get("hidden_mood").getOid());
+ }
+
+ int otherOid =
+ visibleTypeOid(connection.connection(),
"type_lookup_a.reverse_mood");
+ Assertions.assertEquals(otherOid,
registry.get(otherOid).getOid());
+ } finally {
+ statement.execute("DROP SCHEMA IF EXISTS type_lookup_a
CASCADE");
+ statement.execute("DROP SCHEMA IF EXISTS type_lookup_b
CASCADE");
+ }
+ }
+ }
+ }
+
+ private int visibleTypeOid(Connection connection, String name) throws
SQLException {
+ try (PreparedStatement statement =
+ connection.prepareStatement("SELECT to_regtype(?)::oid")) {
+ statement.setString(1, name);
+ try (ResultSet resultSet = statement.executeQuery()) {
+ Assertions.assertTrue(resultSet.next());
+ return resultSet.getInt(1);
+ }
+ }
+ }
+
/**
* The Postgres container is shared across all test methods in this class,
so stale generated
* replication slots must be dropped before the next CDC job starts.