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

davidzollo 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 34940bf66c [Feature][Connector-V2][CDC] Add include/exclude filtering 
for schema change event types  #11044 (#11108)
34940bf66c is described below

commit 34940bf66cd4fd6b07f28fe4b5eb3e6211587b71
Author: Shuai Liu <[email protected]>
AuthorDate: Thu Jun 18 08:41:31 2026 +0800

    [Feature][Connector-V2][CDC] Add include/exclude filtering for schema 
change event types  #11044 (#11108)
    
    Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
 docs/en/connectors/source/MySQL-CDC.md             |  39 +++
 docs/en/connectors/source/Oracle-CDC.md            |  38 +++
 docs/en/connectors/source/SqlServer-CDC.md         |  39 +++
 docs/zh/connectors/source/MySQL-CDC.md             |  38 +++
 docs/zh/connectors/source/Oracle-CDC.md            |  37 +++
 docs/zh/connectors/source/SqlServer-CDC.md         |  38 +++
 .../connectors/cdc/base/option/SourceOptions.java  |  25 ++
 .../cdc/base/schema/SchemaChangeEventFilter.java   | 174 ++++++++++++
 .../cdc/base/schema/SchemaChangeEventType.java     |  95 +++++++
 .../source/BaseChangeStreamTableSourceFactory.java |   3 +
 .../row/SeaTunnelRowDebeziumDeserializeSchema.java |  18 ++
 .../base/schema/SchemaChangeEventFilterTest.java   | 308 +++++++++++++++++++++
 .../cdc/mysql/source/MySqlIncrementalSource.java   |   2 +
 .../source/MySqlIncrementalSourceFactory.java      |   2 +
 .../cdc/oracle/source/OracleIncrementalSource.java |   2 +
 .../source/OracleIncrementalSourceFactory.java     |   4 +-
 .../source/SqlServerIncrementalSource.java         |   2 +
 .../source/SqlServerIncrementalSourceFactory.java  |   4 +-
 .../cdc/mysql/MysqlCDCWithSchemaChangeIT.java      | 121 ++++++++
 .../src/test/resources/ddl/add_columns_filter.sql  |  24 ++
 .../src/test/resources/ddl/drop_columns_filter.sql |  29 ++
 .../src/test/resources/ddl/shop.sql                |   8 +
 ...ysqlcdc_to_mysql_with_schema_change_filter.conf |  53 ++++
 23 files changed, 1101 insertions(+), 2 deletions(-)

diff --git a/docs/en/connectors/source/MySQL-CDC.md 
b/docs/en/connectors/source/MySQL-CDC.md
index 2210231123..5ee7a0c647 100644
--- a/docs/en/connectors/source/MySQL-CDC.md
+++ b/docs/en/connectors/source/MySQL-CDC.md
@@ -215,6 +215,8 @@ When an initial consistent snapshot is made for large 
databases, your establishe
 | exactly_once                              | Boolean  | No       | false   | 
Enable exactly once semantic.                                                   
                                                                                
                                                                                
                                                                                
                                                                                
               [...]
 | format                                    | Enum     | No       | DEFAULT | 
Optional output format for MySQL CDC, valid enumerations are 
`DEFAULT`、`COMPATIBLE_DEBEZIUM_JSON`.                                           
                                                                                
                                                                                
                                                                                
                                  [...]
 | schema-changes.enabled                    | Boolean  | No       | false   | 
Schema evolution is disabled by default. Now we only support `add column`、`drop 
column`、`rename column` and `modify column`.                                    
                                                                                
                                                                                
                                                                                
               [...]
+| schema-changes.include                     | List     | No       | -       | 
Only the listed schema change event types are sent downstream (when 
`schema-changes.enabled = true`). Empty means all are eligible. See [Schema 
change event filtering](#schema-change-event-filtering).                        
                                                                                
                                                                                
                              [...]
+| schema-changes.exclude                     | List     | No       | -       | 
Schema change event types listed here are NOT sent downstream. Applied after 
`schema-changes.include`; exclude wins on conflict. See [Schema change event 
filtering](#schema-change-event-filtering).                                     
                                                                                
                                                                                
                    [...]
 | debezium                                  | Config   | No       | -       | 
Pass-through [Debezium's 
properties](https://github.com/debezium/debezium/blob/v1.9.8.Final/documentation/modules/ROOT/pages/connectors/mysql.adoc#connector-properties)
 to Debezium Embedded Engine which is used to capture data changes from MySQL 
server.                                                                         
                                                                                
         [...]
 | int_type_narrowing                        | Boolean  | No       | true    | 
Int type narrowing, if true, the tinyint(1) type will be narrowed to the 
boolean type if without loss of precision. Support for MySQL at now. Please 
refer to `int_type_narrowing` below                                             
                                                                                
                                                                                
                          [...]
 | common-options                            |          | no       | -       | 
Source plugin common parameters, please refer to [Source Common 
Options](../common-options/source-common-options.md) for details                
                                                                                
                                                                                
                                                                                
                               [...]
@@ -340,6 +342,43 @@ sink {
 }
 
 ```
+
+### Schema change event filtering
+
+When `schema-changes.enabled = true`, you can further control which schema 
change event types are
+propagated downstream using `schema-changes.include` / 
`schema-changes.exclude`. 
+
+Use these SeaTunnel-owned canonical names:
+
+| Canonical name  | Operation                                                  
               |
+|-----------------|---------------------------------------------------------------------------|
+| `add.column`    | add a column                                               
               |
+| `drop.column`   | drop a column                                              
               |
+| `modify.column` | change a column's type/attributes, name unchanged          
               |
+| `change.column` | rename a column, optionally re-type                        
               |
+| `update.columns`| group alias for all four column-level changes above        
                |
+
+Precedence is deterministic:
+
+1. if `schema-changes.include` is set, only included event types are eligible;
+2. `schema-changes.exclude` is then applied;
+3. **exclude wins** when a type appears in both lists.
+
+```hocon
+source {
+  MySQL-CDC {
+    # ...
+    schema-changes.enabled = true
+    schema-changes.include = ["add.column", "drop.column"]
+    schema-changes.exclude = ["change.column"]
+  }
+}
+```
+
+**Data handling when `drop.column` is excluded. For a retained **NOT NULL** 
column the `NULL` write is rejected
+by the sink, so excluding `drop.column` for a NOT NULL column that the source 
has stopped supplying
+will fail at the sink.
+
 ### Support table-pattern for multi-table reading
 
 > `table-pattern` and `table-names` are mutually exclusive
diff --git a/docs/en/connectors/source/Oracle-CDC.md 
b/docs/en/connectors/source/Oracle-CDC.md
index c67fec3a1a..5393335b44 100644
--- a/docs/en/connectors/source/Oracle-CDC.md
+++ b/docs/en/connectors/source/Oracle-CDC.md
@@ -254,6 +254,8 @@ exit;
 | skip_analyze                              | Boolean  | No        | false   | 
Skip the analysis of table count in full stage.In this scenario, you schedule 
analysis table sql to update related table statistics periodically or your 
table data does not change frequently                                           
                                                                                
                                                                                
                     [...]
 | format                                    | Enum     | No        | DEFAULT | 
Optional output format for Oracle CDC, valid enumerations are 
`DEFAULT`、`COMPATIBLE_DEBEZIUM_JSON`.                                           
                                                                                
                                                                                
                                                                                
                                [...]
 | schema-changes.enabled                    | Boolean  | No        | false   | 
Schema evolution is disabled by default. Now we only support `add column`、`drop 
column`、`rename column` and `modify column`.                                    
                                                                                
                                                                                
                                                                                
              [...]
+| schema-changes.include                     | List     | No        | -       
| Only the listed schema change event types are sent downstream (when 
`schema-changes.enabled = true`). Empty means all are eligible. See [Schema 
change event filtering](#schema-change-event-filtering).                        
                                                                                
                                                                                
                             [...]
+| schema-changes.exclude                     | List     | No        | -       
| Schema change event types listed here are NOT sent downstream. Applied after 
`schema-changes.include`; exclude wins on conflict. See [Schema change event 
filtering](#schema-change-event-filtering).                                     
                                                                                
                                                                                
                   [...]
 | debezium                                  | Config   | No        | -       | 
Pass-through [Debezium's 
properties](https://github.com/debezium/debezium/blob/v1.9.8.Final/documentation/modules/ROOT/pages/connectors/oracle.adoc#connector-properties)
 to Debezium Embedded Engine which is used to capture data changes from Oracle 
server.                                                                         
                                                                                
      [...]
 | common-options                            |          | no        | -       | 
Source plugin common parameters, please refer to [Source Common 
Options](../common-options/source-common-options.md) for details                
                                                                                
                                                                                
                                                                                
                              [...]
 | decimal_type_narrowing                    | Boolean | No        | true       
     | Decimal type narrowing, if true, the decimal type will be narrowed to 
the int or long type if without loss of precision. Only support for Oracle at 
now. Please refer to `decimal_type_narrowing` below                             
                                                                                
                                                                                
                   [...]
@@ -363,6 +365,42 @@ source {
 }
 ```
 
+### Schema change event filtering
+
+When `schema-changes.enabled = true`, you can further control which schema 
change event types are
+propagated downstream using `schema-changes.include` / 
`schema-changes.exclude`.
+
+Use these SeaTunnel-owned canonical names:
+
+| Canonical name   | Operation                                            |
+|------------------|------------------------------------------------------|
+| `add.column`     | add a column                                         |
+| `drop.column`    | drop a column                                        |
+| `modify.column`  | change a column's type/attributes, name unchanged    |
+| `change.column`  | rename a column, optionally re-type                  |
+| `update.columns` | group alias for all four column-level changes above  |
+
+Precedence is deterministic:
+
+1. if `schema-changes.include` is set, only included event types are eligible;
+2. `schema-changes.exclude` is then applied;
+3. **exclude wins** when a type appears in both lists.
+
+```hocon
+source {
+  Oracle-CDC {
+    # ...
+    schema-changes.enabled = true
+    schema-changes.include = ["add.column", "drop.column"]
+    schema-changes.exclude = ["change.column"]
+  }
+}
+```
+
+**Data handling when `drop.column` is excluded. For a retained **NOT NULL** 
column the `NULL` write is rejected
+by the sink, so excluding `drop.column` for a NOT NULL column that the source 
has stopped supplying
+will fail at the sink.
+
 ### Support debezium-compatible format send to kafka
 
 > Must be used with kafka connector sink, see [compatible debezium 
 > format](../formats/cdc-compatible-debezium-json.md) for details
diff --git a/docs/en/connectors/source/SqlServer-CDC.md 
b/docs/en/connectors/source/SqlServer-CDC.md
index 5ca2cfb2ce..56cb05c390 100644
--- a/docs/en/connectors/source/SqlServer-CDC.md
+++ b/docs/en/connectors/source/SqlServer-CDC.md
@@ -104,6 +104,9 @@ case-sensitive databases, make sure the configured 
identifier case matches the d
 | exactly_once                              | Boolean  | No       | false   | 
Enable exactly once semantic.                                                   
                                                                                
                                                                                
                                                                                
                                                                                
               [...]
 | debezium.*                                | config   | No       | -       | 
Pass-through Debezium's properties to Debezium Embedded Engine which is used to 
capture data changes from SqlServer server.<br/>See more about<br/>the 
[Debezium's SqlServer Connector 
properties](https://github.com/debezium/debezium/blob/1.6/documentation/modules/ROOT/pages/connectors/sqlserver.adoc#connector-properties)
                                                                                
              [...]
 | format                                    | Enum     | No       | DEFAULT | 
Optional output format for SqlServer CDC, valid enumerations are 
"DEFAULT"、"COMPATIBLE_DEBEZIUM_JSON".                                           
                                                                                
                                                                                
                                                                                
                              [...]
+| schema-changes.enabled                    | Boolean  | No       | false   | 
Schema evolution is disabled by default. Now we only support `add column`、`drop 
column`、`rename column` and `modify column`.                                    
                                                                                
                                                                                
                                                                                
               [...]
+| schema-changes.include                     | List     | No       | -       | 
Only the listed schema change event types are sent downstream (when 
`schema-changes.enabled = true`). Empty means all are eligible. See [Schema 
change event filtering](#schema-change-event-filtering).                        
                                                                                
                                                                                
                              [...]
+| schema-changes.exclude                     | List     | No       | -       | 
Schema change event types listed here are NOT sent downstream. Applied after 
`schema-changes.include`; exclude wins on conflict. See [Schema change event 
filtering](#schema-change-event-filtering).                                     
                                                                                
                                                                                
                    [...]
 | common-options                            |          | no       | -       | 
Source plugin common parameters, please refer to [Source Common 
Options](../common-options/source-common-options.md) for details.               
                                                                                
                                                                                
                                                                                
                               [...]
 
 ### Enable Sql Server CDC
@@ -241,6 +244,42 @@ sink {
 }
 ```
 
+### Schema change event filtering
+
+When `schema-changes.enabled = true`, you can further control which schema 
change event types are
+propagated downstream using `schema-changes.include` / 
`schema-changes.exclude`.
+
+Use these SeaTunnel-owned canonical names:
+
+| Canonical name   | Operation                                            |
+|------------------|------------------------------------------------------|
+| `add.column`     | add a column                                         |
+| `drop.column`    | drop a column                                        |
+| `modify.column`  | change a column's type/attributes, name unchanged    |
+| `change.column`  | rename a column, optionally re-type                  |
+| `update.columns` | group alias for all four column-level changes above  |
+
+Precedence is deterministic:
+
+1. if `schema-changes.include` is set, only included event types are eligible;
+2. `schema-changes.exclude` is then applied;
+3. **exclude wins** when a type appears in both lists.
+
+```hocon
+source {
+  SqlServer-CDC {
+    # ...
+    schema-changes.enabled = true
+    schema-changes.include = ["add.column", "drop.column"]
+    schema-changes.exclude = ["change.column"]
+  }
+}
+```
+
+**Data handling when `drop.column` is excluded. For a retained **NOT NULL** 
column the `NULL` write is rejected
+by the sink, so excluding `drop.column` for a NOT NULL column that the source 
has stopped supplying
+will fail at the sink.
+
 ## Changelog
 
 <ChangeLog />
diff --git a/docs/zh/connectors/source/MySQL-CDC.md 
b/docs/zh/connectors/source/MySQL-CDC.md
index c08e6596ce..b8c27ecc0f 100644
--- a/docs/zh/connectors/source/MySQL-CDC.md
+++ b/docs/zh/connectors/source/MySQL-CDC.md
@@ -214,6 +214,8 @@ show variables where variable_name in ('log_bin', 
'binlog_format', 'binlog_row_i
 | exactly_once                              | Boolean  | 否    | false   | 
启用精确一次语义.                                                                       
                                                                                
                                                                             |
 | format                                    | Enum     | 否    | DEFAULT | 
MySQL CDC 的可选输出格式, 有效的枚举值为 `DEFAULT`、`COMPATIBLE_DEBEZIUM_JSON`.                
                                                                                
                                                                             |
 | schema-changes.enabled                    | Boolean  | 否    | false   | 
模式演进默认是禁用的. 当前我们只支持 `add column`、`drop column`、`rename column` 和 `modify 
column`.                                                                        
                                                                                
    |
+| schema-changes.include                     | List     | 否    | -       | 
仅向下游发送列出的 schema change 事件类型(需 `schema-changes.enabled = true`)。为空表示全部允许。详见 
[Schema change 事件过滤](#schema-change-事件过滤)。                                      
                                                                        |
+| schema-changes.exclude                     | List     | 否    | -       | 
此处列出的 schema change 事件类型不会发送到下游。在 `schema-changes.include` 之后应用;冲突时 exclude 
优先。详见 [Schema change 事件过滤](#schema-change-事件过滤)。                                
                                                                       |
 | debezium                                  | Config   | 否    | -       | 传递 
[Debezium的属性](https://github.com/debezium/debezium/blob/v1.9.8.Final/documentation/modules/ROOT/pages/connectors/mysql.adoc#connector-properties)
 给Debezium嵌入式引擎, 该引擎用于捕获 MySQL 服务的数据变更.                                         
         |
 | int_type_narrowing                        | Boolean  | 否    | true    | 
Int类型收窄,如果为 true,则 tinyint(1) 类型将被收窄为 boolean 类型(如果没有精度损失)。目前仅支持 MySQL。         
                                                                                
                                                                             |
 | common-options                            |          | 否    | -       | 
Source插件通用参数, 详见 [Source Common 
Options](../common-options/source-common-options.md)                            
                                                                                
                                                            |
@@ -339,6 +341,42 @@ sink {
 }
 
 ```
+
+### Schema change 事件过滤
+
+当 `schema-changes.enabled = true` 时,可通过 `schema-changes.include` / 
`schema-changes.exclude` 进一步
+控制哪些 schema change 事件类型会被发送到下游。
+
+使用以下 SeaTunnel 统一的规范名称
+
+| 规范名称        | 操作                                                        |
+|-----------------|-------------------------------------------------------------|
+| `add.column`    | 新增列                                                      |
+| `drop.column`   | 删除列                                                      |
+| `modify.column`  | 修改列的类型/属性,列名不变                  |
+| `change.column`  | 列重命名,可同时改类型                       |
+| `update.columns` | 上述四种列级变更的分组别名                   |
+
+优先级规则(确定性):
+
+1. 若设置了 `schema-changes.include`,则只有被包含的事件类型才有资格;
+2. 然后应用 `schema-changes.exclude`;
+3. 当某类型同时出现在两个列表中时,**exclude 优先**。
+
+```hocon
+source {
+  MySQL-CDC {
+    # ...
+    schema-changes.enabled = true
+    schema-changes.include = ["add.column", "drop.column"]
+    schema-changes.exclude = ["change.column"]
+  }
+}
+```
+
+**排除 `drop.column` 时的数据处理方式。对于被保留的 **NOT NULL** 列,写入 `NULL` 会被 sink 
拒绝,因此对一个源端已不再供数的
+NOT NULL 列排除 `drop.column` 会在 sink 端失败。
+
 ### 表名支持正则以读取多个表
 
 > `table-pattern` 和 `table-names` 只能选择一个
diff --git a/docs/zh/connectors/source/Oracle-CDC.md 
b/docs/zh/connectors/source/Oracle-CDC.md
index d542a5057f..534144ce30 100644
--- a/docs/zh/connectors/source/Oracle-CDC.md
+++ b/docs/zh/connectors/source/Oracle-CDC.md
@@ -253,6 +253,8 @@ exit;
 | skip_analyze                              | Boolean  | 否      | false   | 
在全量阶段跳过表行数的分析。在这种情况下,您需要定期调度分析表 SQL 以更新相关表统计信息,或者您的表数据更改不频繁。                    
                                                                                
                                                                                
                                                                                
                                                                                
                 [...]
 | format                                    | Enum     | 否      | DEFAULT | 
Oracle CDC 的可选输出格式,有效枚举值为 `DEFAULT`、`COMPATIBLE_DEBEZIUM_JSON`。                 
                                                                                
                                                                                
                                                                                
                                                                                
                 [...]
 | schema-changes.enabled                    | Boolean  | 否      | false   | 
Schema 演进默认禁用。目前我们仅支持 `add column`、`drop column`、`rename column` 和 `modify 
column`。                                                                        
                                                                                
                                                                                
                                                                                
                      [...]
+| schema-changes.include                     | List     | 否      | -       | 
仅向下游发送列出的 schema change 事件类型(需 `schema-changes.enabled = true`)。为空表示全部允许。详见 
[Schema change 事件过滤](#schema-change-事件过滤)。                                      
                                                                                
                                                                                
                                                                                
                    [...]
+| schema-changes.exclude                     | List     | 否      | -       | 
此处列出的 schema change 事件类型不会发送到下游。在 `schema-changes.include` 之后应用;冲突时 exclude 
优先。详见 [Schema change 事件过滤](#schema-change-事件过滤)。                                
                                                                                
                                                                                
                                                                                
                    [...]
 | debezium                                  | Config   | 否      | -       | 透传 
[Debezium 
属性](https://github.com/debezium/debezium/blob/v1.9.8.Final/documentation/modules/ROOT/pages/connectors/oracle.adoc#connector-properties)
 给 Debezium Embedded Engine,该引擎用于捕获 Oracle 服务器的数据更改。                            
                                                                                
                                                                                
                            [...]
 | common-options                            |          | 否      | -       | 
源端插件常用参数,详情请参阅 [源端常用选项](../common-options/source-common-options.md)。            
                                                                                
                                                                                
                                                                                
                                                                                
                 [...]
 | decimal_type_narrowing                    | Boolean | 否      | true          
  | 数值类型收缩,如果为 true,则在不损失精度的情况下,将 decimal 类型收缩为 int 或 long 类型。目前仅支持 
Oracle。请参阅下文的 `decimal_type_narrowing`。                                         
                                                                                
                                                                                
                                                                                
                          [...]
@@ -362,6 +364,41 @@ source {
 }
 ```
 
+### Schema change 事件过滤
+
+当 `schema-changes.enabled = true` 时,可通过 `schema-changes.include` / 
`schema-changes.exclude` 进一步
+控制哪些 schema change 事件类型会被发送到下游。过滤只影响“发往下游”的部分。
+
+使用以下 SeaTunnel 统一的规范名称:
+
+| 规范名称         | 操作                                        |
+|------------------|---------------------------------------------|
+| `add.column`     | 新增列                                      |
+| `drop.column`    | 删除列                                      |
+| `modify.column`  | 修改列的类型/属性,列名不变                  |
+| `change.column`  | 列重命名,可同时改类型                       |
+| `update.columns` | 上述四种列级变更的分组别名                   |
+
+优先级规则(确定性):
+
+1. 若设置了 `schema-changes.include`,则只有被包含的事件类型才有资格;
+2. 然后应用 `schema-changes.exclude`;
+3. 当某类型同时出现在两个列表中时,**exclude 优先**。
+
+```hocon
+source {
+  Oracle-CDC {
+    # ...
+    schema-changes.enabled = true
+    schema-changes.include = ["add.column", "drop.column"]
+    schema-changes.exclude = ["change.column"]
+  }
+}
+```
+
+**排除 `drop.column` 时的数据处理方式。对于被保留的 **NOT NULL** 列,写入 `NULL` 会被 sink 
拒绝,因此对一个源端已不再供数的
+NOT NULL 列排除 `drop.column` 会在 sink 端失败。
+
 ### 支持以兼容 debezium 的格式发送到 kafka
 
 > 必须与 kafka 连接器 sink 配合使用,详情请参阅 [兼容 debezium 
 > 格式](../formats/cdc-compatible-debezium-json.md)
diff --git a/docs/zh/connectors/source/SqlServer-CDC.md 
b/docs/zh/connectors/source/SqlServer-CDC.md
index ee849ea7f4..c7453e8d14 100644
--- a/docs/zh/connectors/source/SqlServer-CDC.md
+++ b/docs/zh/connectors/source/SqlServer-CDC.md
@@ -102,6 +102,9 @@ Sql Server CDC 连接器允许从 SqlServer 数据库读取快照数据和增量
 | exactly_once                                   | Boolean  | 否       | false  
 | 启用精确一次语义。                                                                    
                                                                                
                                                                                
                                                              |
 | debezium.*                                     | config   | 否       | -      
 | 将 Debezium 的属性传递给 Debezium Embedded Engine,用于捕获来自 SqlServer 
服务器的数据变更。<br/>了解更多关于<br/>[Debezium 的 SqlServer 
连接器属性](https://github.com/debezium/debezium/blob/1.6/documentation/modules/ROOT/pages/connectors/sqlserver.adoc#connector-properties)
                                 |
 | format                                         | Enum     | 否       | 
DEFAULT | SqlServer CDC 的可选输出格式,有效枚举为 "DEFAULT"、"COMPATIBLE_DEBEZIUM_JSON"。     
                                                                                
                                                                                
                                                               |
+| schema-changes.enabled                         | Boolean  | 否       | false  
 | 模式演进默认是禁用的。当前我们只支持 `add column`、`drop column`、`rename column` 和 `modify 
column`。                                                                        
                                                                                
                                                        |
+| schema-changes.include                          | List     | 否       | -     
  | 仅向下游发送列出的 schema change 事件类型(需 `schema-changes.enabled = true`)。为空表示全部允许。详见 
[Schema change 事件过滤](#schema-change-事件过滤)。                                      
                                                                                
                                    |
+| schema-changes.exclude                          | List     | 否       | -     
  | 此处列出的 schema change 事件类型不会发送到下游。在 `schema-changes.include` 之后应用;冲突时 exclude 
优先。详见 [Schema change 事件过滤](#schema-change-事件过滤)。                                
                                                                                
                                   |
 | common-options                                 |          | 否       | -      
 | 源插件通用参数,请参考 [源通用选项](../common-options/source-common-options.md) 获取详细信息。      
                                                                                
                                                                                
                                                               |
 
 ### 启用 Sql Server CDC
@@ -239,6 +242,41 @@ sink {
 }
 ```
 
+### Schema change 事件过滤
+
+当 `schema-changes.enabled = true` 时,可通过 `schema-changes.include` / 
`schema-changes.exclude` 进一步
+控制哪些 schema change 事件类型会被发送到下游。
+
+使用以下 SeaTunnel 统一的规范名称:
+
+| 规范名称         | 操作                                        |
+|------------------|---------------------------------------------|
+| `add.column`     | 新增列                                      |
+| `drop.column`    | 删除列                                      |
+| `modify.column`  | 修改列的类型/属性,列名不变                  |
+| `change.column`  | 列重命名,可同时改类型                       |
+| `update.columns` | 上述四种列级变更的分组别名                   |
+
+优先级规则(确定性):
+
+1. 若设置了 `schema-changes.include`,则只有被包含的事件类型才有资格;
+2. 然后应用 `schema-changes.exclude`;
+3. 当某类型同时出现在两个列表中时,**exclude 优先**。
+
+```hocon
+source {
+  SqlServer-CDC {
+    # ...
+    schema-changes.enabled = true
+    schema-changes.include = ["add.column", "drop.column"]
+    schema-changes.exclude = ["change.column"]
+  }
+}
+```
+
+**排除 `drop.column` 时的数据处理方式。对于被保留的 **NOT NULL** 列,写入 `NULL` 会被 sink 
拒绝,因此对一个源端已不再供数的
+NOT NULL 列排除 `drop.column` 会在 sink 端失败。
+
 ## 变更日志
 
 <ChangeLog />
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/SourceOptions.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/SourceOptions.java
index 013d798642..97b229aedf 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/SourceOptions.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/SourceOptions.java
@@ -20,8 +20,11 @@ package org.apache.seatunnel.connectors.cdc.base.option;
 import org.apache.seatunnel.api.configuration.Option;
 import org.apache.seatunnel.api.configuration.Options;
 import org.apache.seatunnel.api.configuration.util.OptionRule;
+import org.apache.seatunnel.connectors.cdc.base.schema.SchemaChangeEventType;
 import org.apache.seatunnel.connectors.cdc.debezium.DeserializeFormat;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 @SuppressWarnings("MagicNumber")
@@ -121,6 +124,28 @@ public class SourceOptions {
                     .withDescription(
                             "Enable send schema change events, by default is 
false. If set to true, the schema changes will be sent to downstream.");
 
+    public static final Option<List<String>> SCHEMA_CHANGES_INCLUDE =
+            Options.key("schema-changes.include")
+                    .listType()
+                    .defaultValue(Collections.emptyList())
+                    .withDescription(
+                            "Only schema change event types listed here are 
sent downstream when schema-changes.enabled is true. "
+                                    + "Empty means all event types are 
eligible. "
+                                    + "Valid values: "
+                                    + SchemaChangeEventType.validNames()
+                                    + " (update.columns is a group alias for 
all column-level changes). ");
+
+    public static final Option<List<String>> SCHEMA_CHANGES_EXCLUDE =
+            Options.key("schema-changes.exclude")
+                    .listType()
+                    .defaultValue(Collections.emptyList())
+                    .withDescription(
+                            "Schema change event types listed here are NOT 
sent downstream. Applied after schema-changes.include; "
+                                    + "exclude wins when a type appears in 
both lists. "
+                                    + "Valid values: "
+                                    + SchemaChangeEventType.validNames()
+                                    + " (update.columns is a group alias for 
all column-level changes). ");
+
     public static OptionRule.Builder getBaseRule() {
         return OptionRule.builder()
                 .optional(FORMAT)
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeEventFilter.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeEventFilter.java
new file mode 100644
index 0000000000..3ebfb4b551
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeEventFilter.java
@@ -0,0 +1,174 @@
+/*
+ * 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.seatunnel.connectors.cdc.base.schema;
+
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.event.EventType;
+import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent;
+import org.apache.seatunnel.api.table.schema.event.AlterTableColumnsEvent;
+import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent;
+import org.apache.seatunnel.connectors.cdc.base.option.SourceOptions;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Include/exclude filter for CDC schema change event types.
+ *
+ * <p>Applied after schema change events are normalized to the SeaTunnel event 
model, and before
+ * they are emitted to downstream schema-change coordination. A filtered-out 
event is neither
+ * forwarded to downstream coordination nor applied to the produced schema, so 
the produced row
+ * shape stays in lockstep with the (filtered) sink schema — this is what 
keeps a column whose
+ * {@code drop.column} was suppressed by {@code exclude} present on both sides.
+ *
+ * <p>Precedence (deterministic):
+ *
+ * <ol>
+ *   <li>if {@code include} is non-empty, only included event types are 
eligible;
+ *   <li>{@code exclude} is then applied;
+ *   <li>{@code exclude} wins when the same type appears in both lists.
+ * </ol>
+ *
+ * <p>{@code update.columns} acts as a group alias for all column-level 
changes ({@code add.column},
+ * {@code drop.column}, {@code modify.column}, {@code change.column}): 
including it admits the whole
+ * group, excluding it suppresses the whole group.
+ */
+public final class SchemaChangeEventFilter implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private final Set<EventType> includeTypes;
+    private final Set<EventType> excludeTypes;
+
+    public SchemaChangeEventFilter(Set<EventType> includeTypes, Set<EventType> 
excludeTypes) {
+        this.includeTypes = new HashSet<>(includeTypes);
+        this.excludeTypes = new HashSet<>(excludeTypes);
+    }
+
+    /** Builds a filter from the {@code schema-changes.include} / {@code 
exclude} options. */
+    public static SchemaChangeEventFilter fromConfig(ReadonlyConfig config) {
+        List<String> include = 
config.get(SourceOptions.SCHEMA_CHANGES_INCLUDE);
+        List<String> exclude = 
config.get(SourceOptions.SCHEMA_CHANGES_EXCLUDE);
+        return new SchemaChangeEventFilter(
+                SchemaChangeEventType.fromCanonicalNames(include),
+                SchemaChangeEventType.fromCanonicalNames(exclude));
+    }
+
+    /**
+     * Validates the {@code schema-changes.include} / {@code 
schema-changes.exclude} option values.
+     *
+     * <p>Invoked at job submission time (from the source factory) so an 
unknown canonical name —
+     * e.g. a typo such as {@code rename.tabble} — fails fast during 
submission with a message
+     * listing the valid names, instead of bypassing submission-time option 
validation and failing
+     * later during source initialization.
+     */
+    public static void validateOptions(ReadonlyConfig config) {
+        validateNames(
+                SourceOptions.SCHEMA_CHANGES_INCLUDE.key(),
+                config.get(SourceOptions.SCHEMA_CHANGES_INCLUDE));
+        validateNames(
+                SourceOptions.SCHEMA_CHANGES_EXCLUDE.key(),
+                config.get(SourceOptions.SCHEMA_CHANGES_EXCLUDE));
+    }
+
+    private static void validateNames(String optionKey, List<String> names) {
+        try {
+            SchemaChangeEventType.fromCanonicalNames(names);
+        } catch (IllegalArgumentException e) {
+            throw new IllegalArgumentException(
+                    "Invalid value for option '" + optionKey + "'. " + 
e.getMessage(), e);
+        }
+    }
+
+    public boolean isNoOp() {
+        return includeTypes.isEmpty() && excludeTypes.isEmpty();
+    }
+
+    /**
+     * Applies the filter to a normalized schema change event.
+     *
+     * @return the original event when fully eligible, a reduced {@link 
AlterTableColumnsEvent} when
+     *     only some of its column sub-events are eligible, or {@code null} 
when the whole event is
+     *     filtered out.
+     */
+    public SchemaChangeEvent filter(SchemaChangeEvent event) {
+        if (event == null || isNoOp()) {
+            return event;
+        }
+
+        if (event instanceof AlterTableColumnsEvent) {
+            AlterTableColumnsEvent composite = (AlterTableColumnsEvent) event;
+            List<AlterTableColumnEvent> survivors =
+                    composite.getEvents().stream()
+                            .filter(sub -> 
isColumnLevelEligible(sub.getEventType()))
+                            .collect(Collectors.toList());
+            if (survivors.isEmpty()) {
+                return null;
+            }
+            if (survivors.size() == composite.getEvents().size()) {
+                return composite;
+            }
+            return rebuildComposite(composite, survivors);
+        }
+
+        if (event instanceof AlterTableColumnEvent) {
+            // A standalone column-level event (not wrapped in a composite).
+            return isColumnLevelEligible(event.getEventType()) ? event : null;
+        }
+
+        // Table-level events. No canonical name currently maps to a 
table-level type (rename.table
+        // is not exposed yet), so this is defensive: such events are not 
produced today, but if one
+        // arrives it is filtered consistently with the include/exclude 
precedence.
+        return isEligible(event.getEventType()) ? event : null;
+    }
+
+    /** Eligibility for table-level event types (no group alias). */
+    private boolean isEligible(EventType type) {
+        boolean included = includeTypes.isEmpty() || 
includeTypes.contains(type);
+        return included && !excludeTypes.contains(type);
+    }
+
+    /**
+     * Eligibility for column-level event types, honoring {@code 
update.columns} as the group alias
+     * for the whole column-change family.
+     */
+    private boolean isColumnLevelEligible(EventType type) {
+        boolean included =
+                includeTypes.isEmpty()
+                        || includeTypes.contains(type)
+                        || 
includeTypes.contains(EventType.SCHEMA_CHANGE_UPDATE_COLUMNS);
+        boolean excluded =
+                excludeTypes.contains(type)
+                        || 
excludeTypes.contains(EventType.SCHEMA_CHANGE_UPDATE_COLUMNS);
+        return included && !excluded;
+    }
+
+    private static AlterTableColumnsEvent rebuildComposite(
+            AlterTableColumnsEvent original, List<AlterTableColumnEvent> 
survivors) {
+        AlterTableColumnsEvent reduced =
+                new AlterTableColumnsEvent(original.tableIdentifier(), 
survivors);
+        reduced.setStatement(original.getStatement());
+        reduced.setSourceDialectName(original.getSourceDialectName());
+        reduced.setChangeAfter(original.getChangeAfter());
+        reduced.setJobId(original.getJobId());
+        return reduced;
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeEventType.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeEventType.java
new file mode 100644
index 0000000000..dec081d9bc
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeEventType.java
@@ -0,0 +1,95 @@
+/*
+ * 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.seatunnel.connectors.cdc.base.schema;
+
+import org.apache.seatunnel.api.event.EventType;
+
+import java.util.ArrayList;
+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 java.util.stream.Collectors;
+
+/**
+ * Maps the user-facing canonical names used in {@code schema-changes.include} 
/ {@code
+ * schema-changes.exclude} to the internal {@link EventType} enum, so users 
never need to know
+ * internal class or enum names. {@code update.columns} is a group alias for 
all column-level
+ * changes.
+ */
+public final class SchemaChangeEventType {
+
+    private static final Map<String, EventType> CANONICAL_NAME_TO_EVENT_TYPE;
+
+    static {
+        Map<String, EventType> map = new LinkedHashMap<>();
+        map.put("add.column", EventType.SCHEMA_CHANGE_ADD_COLUMN);
+        map.put("drop.column", EventType.SCHEMA_CHANGE_DROP_COLUMN);
+        map.put("modify.column", EventType.SCHEMA_CHANGE_MODIFY_COLUMN);
+        map.put("change.column", EventType.SCHEMA_CHANGE_CHANGE_COLUMN);
+        map.put("update.columns", EventType.SCHEMA_CHANGE_UPDATE_COLUMNS);
+        // NOTE: rename.table (SCHEMA_CHANGE_RENAME_TABLE) is intentionally 
NOT exposed yet. CDC has
+        // no end-to-end handling for table renames: the DDL is never parsed 
into an
+        // AlterTableNameEvent, the schema handlers treat that event as a 
no-op, and no sink applies
+        // it. Exposing it as a filterable name would advertise a capability 
that does not exist.
+        // It should be added back only once table-rename is implemented 
end-to-end (see the
+        // rename-table design follow-up).
+        CANONICAL_NAME_TO_EVENT_TYPE = Collections.unmodifiableMap(map);
+    }
+
+    private SchemaChangeEventType() {}
+
+    public static String validNames() {
+        return String.join(", ", CANONICAL_NAME_TO_EVENT_TYPE.keySet());
+    }
+
+    public static EventType fromCanonicalName(String canonicalName) {
+        if (canonicalName == null) {
+            throw new IllegalArgumentException(
+                    "Schema change event type name must not be null. Valid 
names are: "
+                            + validNames());
+        }
+        String normalized = canonicalName.trim().toLowerCase();
+        EventType eventType = CANONICAL_NAME_TO_EVENT_TYPE.get(normalized);
+        if (eventType == null) {
+            throw new IllegalArgumentException(
+                    "Unknown schema change event type '"
+                            + canonicalName
+                            + "'. Valid names are: "
+                            + validNames());
+        }
+        return eventType;
+    }
+
+    public static Set<EventType> fromCanonicalNames(Collection<String> 
canonicalNames) {
+        if (canonicalNames == null || canonicalNames.isEmpty()) {
+            return Collections.emptySet();
+        }
+        return canonicalNames.stream()
+                .map(SchemaChangeEventType::fromCanonicalName)
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+    }
+
+    /** Visible for testing: the supported canonical names. */
+    static List<String> canonicalNames() {
+        return new ArrayList<>(CANONICAL_NAME_TO_EVENT_TYPE.keySet());
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/BaseChangeStreamTableSourceFactory.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/BaseChangeStreamTableSourceFactory.java
index a801a626e1..bc2c4961cd 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/BaseChangeStreamTableSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/BaseChangeStreamTableSourceFactory.java
@@ -25,6 +25,7 @@ import org.apache.seatunnel.api.table.connector.TableSource;
 import org.apache.seatunnel.api.table.factory.ChangeStreamTableSourceFactory;
 import org.apache.seatunnel.api.table.factory.ChangeStreamTableSourceState;
 import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext;
+import org.apache.seatunnel.connectors.cdc.base.schema.SchemaChangeEventFilter;
 import org.apache.seatunnel.connectors.cdc.base.source.split.IncrementalSplit;
 import org.apache.seatunnel.connectors.cdc.base.source.split.SourceSplitBase;
 
@@ -47,6 +48,7 @@ public abstract class BaseChangeStreamTableSourceFactory 
implements ChangeStream
     @Override
     public <T, SplitT extends SourceSplit, StateT extends Serializable>
             TableSource<T, SplitT, StateT> 
createSource(TableSourceFactoryContext context) {
+        SchemaChangeEventFilter.validateOptions(context.getOptions());
         return restoreSource(context, Collections.emptyList());
     }
 
@@ -55,6 +57,7 @@ public abstract class BaseChangeStreamTableSourceFactory 
implements ChangeStream
             TableSource<T, SplitT, StateT> restoreSource(
                     TableSourceFactoryContext context,
                     ChangeStreamTableSourceState<StateT, SplitT> state) {
+        SchemaChangeEventFilter.validateOptions(context.getOptions());
         return restoreSource(context, getRestoreTableStruct(state));
     }
 
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializeSchema.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializeSchema.java
index 4dcfb77078..4c237b1bcd 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializeSchema.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializeSchema.java
@@ -30,6 +30,7 @@ import 
org.apache.seatunnel.api.table.schema.handler.TableSchemaChangeEventHandl
 import org.apache.seatunnel.api.table.type.MetadataUtil;
 import org.apache.seatunnel.api.table.type.RowKind;
 import org.apache.seatunnel.api.table.type.SeaTunnelRow;
+import org.apache.seatunnel.connectors.cdc.base.schema.SchemaChangeEventFilter;
 import org.apache.seatunnel.connectors.cdc.base.schema.SchemaChangeResolver;
 import org.apache.seatunnel.connectors.cdc.base.utils.SourceRecordUtils;
 import 
org.apache.seatunnel.connectors.cdc.debezium.AbstractDebeziumDeserializationSchema;
@@ -72,6 +73,7 @@ public final class SeaTunnelRowDebeziumDeserializeSchema
     private final ZoneId serverTimeZone;
     private final DebeziumDeserializationConverterFactory 
userDefinedConverterFactory;
     private final SchemaChangeResolver schemaChangeResolver;
+    private final SchemaChangeEventFilter schemaChangeEventFilter;
     private final TableSchemaChangeEventHandler tableSchemaChangeHandler;
     private List<CatalogTable> tables;
     private Map<String, SeaTunnelRowDebeziumDeserializationConverters> 
tableRowConverters;
@@ -82,6 +84,7 @@ public final class SeaTunnelRowDebeziumDeserializeSchema
             ZoneId serverTimeZone,
             DebeziumDeserializationConverterFactory 
userDefinedConverterFactory,
             SchemaChangeResolver schemaChangeResolver,
+            SchemaChangeEventFilter schemaChangeEventFilter,
             Map<TableId, Struct> tableIdTableChangeMap) {
         super(tableIdTableChangeMap);
         this.metadataConverters = metadataConverters;
@@ -89,6 +92,7 @@ public final class SeaTunnelRowDebeziumDeserializeSchema
         this.userDefinedConverterFactory = userDefinedConverterFactory;
         this.tables = checkNotNull(tables);
         this.schemaChangeResolver = schemaChangeResolver;
+        this.schemaChangeEventFilter = schemaChangeEventFilter;
         this.tableSchemaChangeHandler = new TableSchemaChangeEventDispatcher();
         this.tableRowConverters =
                 createTableRowConverters(
@@ -136,6 +140,18 @@ public final class SeaTunnelRowDebeziumDeserializeSchema
             log.warn("Unsupported resolve schemaChangeEvent {}, just skip.", 
record);
             return;
         }
+
+        // Filter before updating the produced schema, so the produced row 
shape stays in lockstep
+        // with the (filtered) sink schema. Only surviving events are applied 
below.
+        if (schemaChangeEventFilter != null) {
+            schemaChangeEvent = 
schemaChangeEventFilter.filter(schemaChangeEvent);
+        }
+        if (schemaChangeEvent == null) {
+            log.debug(
+                    "Schema change event is fully filtered out by 
schema-changes.include/exclude, not applied to schema and not sent 
downstream.");
+            return;
+        }
+
         boolean tableExist = false;
         for (int i = 0; i < tables.size(); i++) {
             CatalogTable changeBefore = tables.get(i);
@@ -394,6 +410,7 @@ public final class SeaTunnelRowDebeziumDeserializeSchema
                 DebeziumDeserializationConverterFactory.DEFAULT;
         private Map<TableId, Struct> tableIdTableChangeMap = new HashMap<>();
         private SchemaChangeResolver schemaChangeResolver;
+        private SchemaChangeEventFilter schemaChangeEventFilter;
 
         public SeaTunnelRowDebeziumDeserializeSchema build() {
             return new SeaTunnelRowDebeziumDeserializeSchema(
@@ -402,6 +419,7 @@ public final class SeaTunnelRowDebeziumDeserializeSchema
                     serverTimeZone,
                     userDefinedConverterFactory,
                     schemaChangeResolver,
+                    schemaChangeEventFilter,
                     tableIdTableChangeMap);
         }
     }
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeEventFilterTest.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeEventFilterTest.java
new file mode 100644
index 0000000000..edd42553f0
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeEventFilterTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.seatunnel.connectors.cdc.base.schema;
+
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.event.EventType;
+import org.apache.seatunnel.api.table.catalog.Column;
+import org.apache.seatunnel.api.table.catalog.PhysicalColumn;
+import org.apache.seatunnel.api.table.catalog.TableIdentifier;
+import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent;
+import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent;
+import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent;
+import org.apache.seatunnel.api.table.schema.event.AlterTableColumnsEvent;
+import org.apache.seatunnel.api.table.schema.event.AlterTableDropColumnEvent;
+import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent;
+import org.apache.seatunnel.api.table.schema.event.AlterTableNameEvent;
+import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent;
+import org.apache.seatunnel.api.table.type.BasicType;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+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.stream.Collectors;
+
+class SchemaChangeEventFilterTest {
+
+    private static final TableIdentifier TABLE = TableIdentifier.of("", "db", 
"tbl");
+
+    private static Column column(String name) {
+        return PhysicalColumn.of(name, BasicType.STRING_TYPE, 10L, true, null, 
"");
+    }
+
+    private static SchemaChangeEventFilter filter(List<String> include, 
List<String> exclude) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("schema-changes.include", include);
+        map.put("schema-changes.exclude", exclude);
+        return SchemaChangeEventFilter.fromConfig(ReadonlyConfig.fromMap(map));
+    }
+
+    private static AlterTableColumnsEvent composite(AlterTableColumnEvent... 
events) {
+        return new AlterTableColumnsEvent(TABLE, new 
ArrayList<>(Arrays.asList(events)));
+    }
+
+    private static List<EventType> subTypes(SchemaChangeEvent event) {
+        return ((AlterTableColumnsEvent) event)
+                .getEvents().stream()
+                        .map(AlterTableColumnEvent::getEventType)
+                        .collect(Collectors.toList());
+    }
+
+    @Test
+    void noConfigIsAllowAll() {
+        SchemaChangeEventFilter f = filter(Collections.emptyList(), 
Collections.emptyList());
+        Assertions.assertTrue(f.isNoOp());
+        AlterTableColumnsEvent event =
+                composite(
+                        AlterTableAddColumnEvent.add(TABLE, column("a")),
+                        new AlterTableDropColumnEvent(TABLE, "b"));
+        Assertions.assertSame(event, f.filter(event));
+    }
+
+    @Test
+    void includeOnlyKeepsOnlyListedLeafTypes() {
+        SchemaChangeEventFilter f = filter(Arrays.asList("add.column"), 
Collections.emptyList());
+        SchemaChangeEvent result =
+                f.filter(
+                        composite(
+                                AlterTableAddColumnEvent.add(TABLE, 
column("a")),
+                                new AlterTableDropColumnEvent(TABLE, "b"),
+                                AlterTableModifyColumnEvent.modify(TABLE, 
column("c"))));
+        Assertions.assertEquals(
+                Collections.singletonList(EventType.SCHEMA_CHANGE_ADD_COLUMN), 
subTypes(result));
+    }
+
+    @Test
+    void excludeOnlyDropsListedLeafTypes() {
+        SchemaChangeEventFilter f = filter(Collections.emptyList(), 
Arrays.asList("drop.column"));
+        SchemaChangeEvent result =
+                f.filter(
+                        composite(
+                                AlterTableAddColumnEvent.add(TABLE, 
column("a")),
+                                new AlterTableDropColumnEvent(TABLE, "b")));
+        Assertions.assertEquals(
+                Collections.singletonList(EventType.SCHEMA_CHANGE_ADD_COLUMN), 
subTypes(result));
+    }
+
+    @Test
+    void excludeWinsOverIncludeForSameType() {
+        SchemaChangeEventFilter f =
+                filter(Arrays.asList("add.column", "drop.column"), 
Arrays.asList("add.column"));
+        SchemaChangeEvent result =
+                f.filter(
+                        composite(
+                                AlterTableAddColumnEvent.add(TABLE, 
column("a")),
+                                new AlterTableDropColumnEvent(TABLE, "b")));
+        Assertions.assertEquals(
+                
Collections.singletonList(EventType.SCHEMA_CHANGE_DROP_COLUMN), 
subTypes(result));
+    }
+
+    @Test
+    void wholeEventDroppedWhenNoSubEventSurvives() {
+        SchemaChangeEventFilter f =
+                filter(Collections.emptyList(), Arrays.asList("add.column", 
"drop.column"));
+        SchemaChangeEvent result =
+                f.filter(
+                        composite(
+                                AlterTableAddColumnEvent.add(TABLE, 
column("a")),
+                                new AlterTableDropColumnEvent(TABLE, "b")));
+        Assertions.assertNull(result);
+    }
+
+    @Test
+    void changeColumnIsFilteredIndependentlyOfModifyColumn() {
+        // exclude modify.column must NOT drop a change.column (rename) 
sub-event
+        SchemaChangeEventFilter f = filter(Collections.emptyList(), 
Arrays.asList("modify.column"));
+        SchemaChangeEvent result =
+                f.filter(
+                        composite(
+                                AlterTableModifyColumnEvent.modify(TABLE, 
column("c")),
+                                AlterTableChangeColumnEvent.change(TABLE, 
"old", column("new"))));
+        Assertions.assertEquals(
+                
Collections.singletonList(EventType.SCHEMA_CHANGE_CHANGE_COLUMN), 
subTypes(result));
+    }
+
+    @Test
+    void updateColumnsIncludeIsGroupAliasForAllColumnChanges() {
+        SchemaChangeEventFilter f =
+                filter(Arrays.asList("update.columns"), 
Collections.emptyList());
+        SchemaChangeEvent result =
+                f.filter(
+                        composite(
+                                AlterTableAddColumnEvent.add(TABLE, 
column("a")),
+                                new AlterTableDropColumnEvent(TABLE, "b"),
+                                AlterTableModifyColumnEvent.modify(TABLE, 
column("c"))));
+        Assertions.assertEquals(3, ((AlterTableColumnsEvent) 
result).getEvents().size());
+    }
+
+    @Test
+    void updateColumnsExcludeSuppressesAllColumnChanges() {
+        SchemaChangeEventFilter f =
+                filter(Collections.emptyList(), 
Arrays.asList("update.columns"));
+        SchemaChangeEvent result =
+                f.filter(
+                        composite(
+                                AlterTableAddColumnEvent.add(TABLE, 
column("a")),
+                                AlterTableModifyColumnEvent.modify(TABLE, 
column("c"))));
+        Assertions.assertNull(result);
+    }
+
+    @Test
+    void renameTableIsNotAnExposedCanonicalName() {
+        // rename.table is intentionally not exposed: CDC has no end-to-end 
handling for table
+        // renames, so it must be rejected as an unknown name rather than 
advertised as filterable.
+        IllegalArgumentException ex =
+                Assertions.assertThrows(
+                        IllegalArgumentException.class,
+                        () -> 
SchemaChangeEventType.fromCanonicalName("rename.table"));
+        Assertions.assertTrue(ex.getMessage().contains("rename.table"));
+    }
+
+    @Test
+    void strayTableLevelEventIsHandledDefensively() {
+        // No canonical name maps to a table-level type, so such an event is 
not produced by CDC
+        // today. The filter still handles it defensively: an active 
column-level include list
+        // drops it, and a no-op filter passes it through unchanged.
+        AlterTableNameEvent rename =
+                new AlterTableNameEvent(TABLE, TableIdentifier.of("", "db", 
"tbl2"));
+        Assertions.assertNull(
+                filter(Arrays.asList("add.column"), 
Collections.emptyList()).filter(rename));
+        Assertions.assertSame(
+                rename, filter(Collections.emptyList(), 
Collections.emptyList()).filter(rename));
+    }
+
+    @Test
+    void unknownNameFailsFast() {
+        IllegalArgumentException ex =
+                Assertions.assertThrows(
+                        IllegalArgumentException.class,
+                        () -> filter(Arrays.asList("create.table"), 
Collections.emptyList()));
+        Assertions.assertTrue(ex.getMessage().contains("create.table"));
+        Assertions.assertTrue(ex.getMessage().contains("add.column"));
+    }
+
+    @Test
+    void validateOptionsAcceptsValidNames() {
+        Map<String, Object> map = new HashMap<>();
+        map.put("schema-changes.include", Arrays.asList("add.column"));
+        map.put("schema-changes.exclude", Arrays.asList("drop.column"));
+        Assertions.assertDoesNotThrow(
+                () -> 
SchemaChangeEventFilter.validateOptions(ReadonlyConfig.fromMap(map)));
+    }
+
+    @Test
+    void validateOptionsAcceptsEmptyConfig() {
+        Assertions.assertDoesNotThrow(
+                () ->
+                        SchemaChangeEventFilter.validateOptions(
+                                ReadonlyConfig.fromMap(new HashMap<>())));
+    }
+
+    @Test
+    void validateOptionsFailsFastOnUnknownIncludeName() {
+        Map<String, Object> map = new HashMap<>();
+        map.put("schema-changes.include", Arrays.asList("rename.tabble"));
+        IllegalArgumentException ex =
+                Assertions.assertThrows(
+                        IllegalArgumentException.class,
+                        () -> 
SchemaChangeEventFilter.validateOptions(ReadonlyConfig.fromMap(map)));
+        
Assertions.assertTrue(ex.getMessage().contains("schema-changes.include"));
+        Assertions.assertTrue(ex.getMessage().contains("rename.tabble"));
+        Assertions.assertTrue(ex.getMessage().contains("add.column"));
+    }
+
+    @Test
+    void validateOptionsFailsFastOnUnknownExcludeName() {
+        Map<String, Object> map = new HashMap<>();
+        map.put("schema-changes.exclude", Arrays.asList("drop.colum"));
+        IllegalArgumentException ex =
+                Assertions.assertThrows(
+                        IllegalArgumentException.class,
+                        () -> 
SchemaChangeEventFilter.validateOptions(ReadonlyConfig.fromMap(map)));
+        
Assertions.assertTrue(ex.getMessage().contains("schema-changes.exclude"));
+        Assertions.assertTrue(ex.getMessage().contains("drop.colum"));
+    }
+
+    @Test
+    void namesAreNormalizedAndDeduplicated() {
+        SchemaChangeEventFilter f =
+                filter(Arrays.asList("  ADD.COLUMN  ", "add.column"), 
Collections.emptyList());
+        SchemaChangeEvent result =
+                f.filter(
+                        composite(
+                                AlterTableAddColumnEvent.add(TABLE, 
column("a")),
+                                new AlterTableDropColumnEvent(TABLE, "b")));
+        Assertions.assertEquals(
+                Collections.singletonList(EventType.SCHEMA_CHANGE_ADD_COLUMN), 
subTypes(result));
+    }
+
+    @Test
+    void canonicalNameMappingIsExhaustiveAndStable() {
+        Assertions.assertEquals(
+                EventType.SCHEMA_CHANGE_ADD_COLUMN,
+                SchemaChangeEventType.fromCanonicalName("add.column"));
+        Assertions.assertEquals(
+                EventType.SCHEMA_CHANGE_DROP_COLUMN,
+                SchemaChangeEventType.fromCanonicalName("drop.column"));
+        Assertions.assertEquals(
+                EventType.SCHEMA_CHANGE_MODIFY_COLUMN,
+                SchemaChangeEventType.fromCanonicalName("modify.column"));
+        Assertions.assertEquals(
+                EventType.SCHEMA_CHANGE_CHANGE_COLUMN,
+                SchemaChangeEventType.fromCanonicalName("change.column"));
+        Assertions.assertEquals(
+                EventType.SCHEMA_CHANGE_UPDATE_COLUMNS,
+                SchemaChangeEventType.fromCanonicalName("update.columns"));
+        Assertions.assertEquals(5, 
SchemaChangeEventType.canonicalNames().size());
+    }
+
+    /**
+     * Per-type coverage: for each column-level canonical name, {@code 
include=[name]} must keep
+     * exactly that sub-event out of a composite carrying all four 
column-level changes.
+     */
+    @Test
+    void includeEachColumnLevelTypeKeepsOnlyThatType() {
+        Map<String, EventType> cases = new HashMap<>();
+        cases.put("add.column", EventType.SCHEMA_CHANGE_ADD_COLUMN);
+        cases.put("drop.column", EventType.SCHEMA_CHANGE_DROP_COLUMN);
+        cases.put("modify.column", EventType.SCHEMA_CHANGE_MODIFY_COLUMN);
+        cases.put("change.column", EventType.SCHEMA_CHANGE_CHANGE_COLUMN);
+
+        for (Map.Entry<String, EventType> c : cases.entrySet()) {
+            SchemaChangeEventFilter f =
+                    filter(Collections.singletonList(c.getKey()), 
Collections.emptyList());
+            SchemaChangeEvent result =
+                    f.filter(
+                            composite(
+                                    AlterTableAddColumnEvent.add(TABLE, 
column("a")),
+                                    new AlterTableDropColumnEvent(TABLE, "b"),
+                                    AlterTableModifyColumnEvent.modify(TABLE, 
column("c")),
+                                    AlterTableChangeColumnEvent.change(TABLE, 
"old", column("d"))));
+            Assertions.assertEquals(
+                    Collections.singletonList(c.getValue()),
+                    subTypes(result),
+                    "include=[" + c.getKey() + "] should keep only " + 
c.getValue());
+        }
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSource.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSource.java
index 4a7cb09c9b..abb39f5897 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSource.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSource.java
@@ -31,6 +31,7 @@ import 
org.apache.seatunnel.connectors.cdc.base.dialect.DataSourceDialect;
 import org.apache.seatunnel.connectors.cdc.base.option.JdbcSourceOptions;
 import org.apache.seatunnel.connectors.cdc.base.option.StartupMode;
 import org.apache.seatunnel.connectors.cdc.base.option.StopMode;
+import org.apache.seatunnel.connectors.cdc.base.schema.SchemaChangeEventFilter;
 import org.apache.seatunnel.connectors.cdc.base.source.IncrementalSource;
 import org.apache.seatunnel.connectors.cdc.base.source.offset.OffsetFactory;
 import 
org.apache.seatunnel.connectors.cdc.debezium.ConnectTableChangeSerializer;
@@ -129,6 +130,7 @@ public class MySqlIncrementalSource<T> extends 
IncrementalSource<T, JdbcSourceCo
                         .setTableIdTableChangeMap(tableIdTableChangeMap)
                         .setSchemaChangeResolver(
                                 new 
MySqlSchemaChangeResolver(createSourceConfigFactory(config)))
+                        
.setSchemaChangeEventFilter(SchemaChangeEventFilter.fromConfig(config))
                         .build();
     }
 
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java
index 667f7b0654..04858dcb29 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java
@@ -78,6 +78,8 @@ public class MySqlIncrementalSourceFactory extends 
BaseChangeStreamTableSourceFa
                         MySqlIncrementalSourceOptions.SPLIT_ALLOW_SAMPLING,
                         MySqlIncrementalSourceOptions.TABLE_NAMES_CONFIG,
                         MySqlIncrementalSourceOptions.SCHEMA_CHANGES_ENABLED,
+                        MySqlIncrementalSourceOptions.SCHEMA_CHANGES_INCLUDE,
+                        MySqlIncrementalSourceOptions.SCHEMA_CHANGES_EXCLUDE,
                         MySqlIncrementalSourceOptions.INT_TYPE_NARROWING)
                 .optional(
                         MySqlIncrementalSourceOptions.STARTUP_MODE,
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSource.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSource.java
index fb28bda574..c2c9e860fa 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSource.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSource.java
@@ -31,6 +31,7 @@ import 
org.apache.seatunnel.connectors.cdc.base.option.JdbcSourceOptions;
 import org.apache.seatunnel.connectors.cdc.base.option.SourceOptions;
 import org.apache.seatunnel.connectors.cdc.base.option.StartupMode;
 import org.apache.seatunnel.connectors.cdc.base.option.StopMode;
+import org.apache.seatunnel.connectors.cdc.base.schema.SchemaChangeEventFilter;
 import org.apache.seatunnel.connectors.cdc.base.source.IncrementalSource;
 import org.apache.seatunnel.connectors.cdc.base.source.offset.OffsetFactory;
 import 
org.apache.seatunnel.connectors.cdc.debezium.ConnectTableChangeSerializer;
@@ -112,6 +113,7 @@ public class OracleIncrementalSource<T> extends 
IncrementalSource<T, JdbcSourceC
                         .setServerTimeZone(ZoneId.of(zoneId))
                         .setSchemaChangeResolver(
                                 new 
OracleSchemaChangeResolver(createSourceConfigFactory(config)))
+                        
.setSchemaChangeEventFilter(SchemaChangeEventFilter.fromConfig(config))
                         .setTableIdTableChangeMap(tableIdStructMap)
                         .build();
     }
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSourceFactory.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSourceFactory.java
index 7c0371478c..93e2e7f5b4 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSourceFactory.java
@@ -78,7 +78,9 @@ public class OracleIncrementalSourceFactory extends 
BaseChangeStreamTableSourceF
                         OracleIncrementalSourceOptions.INVERSE_SAMPLING_RATE,
                         OracleIncrementalSourceOptions.SPLIT_ALLOW_SAMPLING,
                         OracleIncrementalSourceOptions.TABLE_NAMES_CONFIG,
-                        OracleIncrementalSourceOptions.SCHEMA_CHANGES_ENABLED)
+                        OracleIncrementalSourceOptions.SCHEMA_CHANGES_ENABLED,
+                        OracleIncrementalSourceOptions.SCHEMA_CHANGES_INCLUDE,
+                        OracleIncrementalSourceOptions.SCHEMA_CHANGES_EXCLUDE)
                 .optional(
                         OracleIncrementalSourceOptions.STARTUP_MODE,
                         OracleIncrementalSourceOptions.STOP_MODE)
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSource.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSource.java
index c80340d99b..2945d26777 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSource.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSource.java
@@ -31,6 +31,7 @@ import 
org.apache.seatunnel.connectors.cdc.base.dialect.DataSourceDialect;
 import org.apache.seatunnel.connectors.cdc.base.option.JdbcSourceOptions;
 import org.apache.seatunnel.connectors.cdc.base.option.StartupMode;
 import org.apache.seatunnel.connectors.cdc.base.option.StopMode;
+import org.apache.seatunnel.connectors.cdc.base.schema.SchemaChangeEventFilter;
 import org.apache.seatunnel.connectors.cdc.base.source.IncrementalSource;
 import org.apache.seatunnel.connectors.cdc.base.source.offset.OffsetFactory;
 import 
org.apache.seatunnel.connectors.cdc.debezium.ConnectTableChangeSerializer;
@@ -119,6 +120,7 @@ public class SqlServerIncrementalSource<T> extends 
IncrementalSource<T, JdbcSour
                         .setTableIdTableChangeMap(tableIdTableChangeMap)
                         .setSchemaChangeResolver(
                                 schemaChangesEnabled ? new 
SqlServerSchemaChangeResolver() : null)
+                        
.setSchemaChangeEventFilter(SchemaChangeEventFilter.fromConfig(config))
                         .build();
     }
 
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSourceFactory.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSourceFactory.java
index 9436bc885f..02e40917b7 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSourceFactory.java
@@ -72,7 +72,9 @@ public class SqlServerIncrementalSourceFactory implements 
TableSourceFactory {
                         
SqlServerIncrementalSourceOptions.INVERSE_SAMPLING_RATE,
                         SqlServerIncrementalSourceOptions.SPLIT_ALLOW_SAMPLING,
                         SqlServerIncrementalSourceOptions.TABLE_NAMES_CONFIG,
-                        
SqlServerIncrementalSourceOptions.SCHEMA_CHANGES_ENABLED)
+                        
SqlServerIncrementalSourceOptions.SCHEMA_CHANGES_ENABLED,
+                        
SqlServerIncrementalSourceOptions.SCHEMA_CHANGES_INCLUDE,
+                        
SqlServerIncrementalSourceOptions.SCHEMA_CHANGES_EXCLUDE)
                 .optional(
                         SqlServerIncrementalSourceOptions.STARTUP_MODE,
                         SqlServerIncrementalSourceOptions.STOP_MODE)
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/MysqlCDCWithSchemaChangeIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/MysqlCDCWithSchemaChangeIT.java
index b865fd8847..d2150a0103 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/MysqlCDCWithSchemaChangeIT.java
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/MysqlCDCWithSchemaChangeIT.java
@@ -51,6 +51,7 @@ import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Stream;
@@ -70,6 +71,9 @@ public class MysqlCDCWithSchemaChangeIT extends TestSuiteBase 
implements TestRes
     private static final String SINK_TABLE = 
"mysql_cdc_e2e_sink_table_with_schema_change";
     private static final String SINK_TABLE2 =
             "mysql_cdc_e2e_sink_table_with_schema_change_exactly_once";
+    private static final String SINK_TABLE_FILTER = 
"mysql_cdc_e2e_sink_table_schema_change_filter";
+    private static final String STABLE_QUERY =
+            "select id,name,description,weight from %s.%s order by id";
     private static final String MYSQL_HOST = "mysql_cdc_e2e";
     private static final String MYSQL_USER_NAME = "mysqluser";
     private static final String MYSQL_USER_PASSWORD = "mysqlpw";
@@ -198,6 +202,123 @@ public class MysqlCDCWithSchemaChangeIT extends 
TestSuiteBase implements TestRes
         assertSchemaEvolution(MYSQL_DATABASE, SOURCE_TABLE, SINK_TABLE2);
     }
 
+    /**
+     * Regression for issue #11044. With {@code schema-changes.exclude = 
["drop.column"]}: a dropped
+     * column must NOT propagate to the sink (it stays in the sink schema), 
and the data changes
+     * happening at the same time must still reach the sink.
+     *
+     * <p>The dropped column here is intentionally <b>NULLABLE</b>. #11044 is 
event-type filtering
+     * only and, per its non-goals, does not define a schema-change 
data-handling policy, so the
+     * source simply writes {@code null} for a retained-but-no-longer-supplied 
column — valid for a
+     * nullable column. Excluding {@code drop.column} for a NOT NULL column is 
a known limitation
+     * that fails at the sink and is deferred to a future behavior-policy 
feature; see MySQL-CDC.md.
+     * Using a nullable column is exactly why this test needs its own {@code 
*_filter} DDL templates
+     * instead of the shared {@code add_columns}/{@code drop_columns} (which 
add/drop NOT NULL
+     * columns).
+     */
+    @Order(3)
+    @TestTemplate
+    public void testMysqlCdcSchemaChangeEventTypeFilter(TestContainer 
container) {
+        shopDatabase.setTemplateName("shop").createAndInitialize();
+        CompletableFuture.runAsync(
+                () -> {
+                    try {
+                        
container.executeJob("/mysqlcdc_to_mysql_with_schema_change_filter.conf");
+                    } catch (Exception e) {
+                        log.error("Commit task exception :" + e.getMessage());
+                        throw new RuntimeException(e);
+                    }
+                });
+
+        // initial snapshot synced
+        await().atMost(30000, TimeUnit.MILLISECONDS)
+                .untilAsserted(
+                        () ->
+                                Assertions.assertIterableEquals(
+                                        query(
+                                                String.format(
+                                                        STABLE_QUERY,
+                                                        MYSQL_DATABASE,
+                                                        SOURCE_TABLE)),
+                                        query(
+                                                String.format(
+                                                        STABLE_QUERY,
+                                                        MYSQL_DATABASE,
+                                                        SINK_TABLE_FILTER))));
+
+        // add.column is NOT excluded
+        
shopDatabase.setTemplateName("add_columns_filter").createAndInitialize();
+        await().atMost(30000, TimeUnit.MILLISECONDS)
+                .untilAsserted(
+                        () ->
+                                Assertions.assertTrue(
+                                        columnExists(
+                                                MYSQL_DATABASE, 
SINK_TABLE_FILTER, "add_column1"),
+                                        "add.column should propagate to the 
sink"));
+
+        // drop.column IS excluded; this template also inserts/updates/deletes 
rows at the same time
+        
shopDatabase.setTemplateName("drop_columns_filter").createAndInitialize();
+
+        // regression: the concurrent data changes must still reach the sink 
(job did not crash)
+        await().atMost(60000, TimeUnit.MILLISECONDS)
+                .untilAsserted(
+                        () ->
+                                Assertions.assertIterableEquals(
+                                        query(
+                                                String.format(
+                                                        STABLE_QUERY,
+                                                        MYSQL_DATABASE,
+                                                        SOURCE_TABLE)),
+                                        query(
+                                                String.format(
+                                                        STABLE_QUERY,
+                                                        MYSQL_DATABASE,
+                                                        SINK_TABLE_FILTER))));
+
+        // Row-level hardening: drop_columns_filter.sql performs INSERT + 
UPDATE + DELETE alongside
+        // the filtered drop.column DDL. Every one of those data changes must 
be reflected in the
+        // sink (the earlier iterable-equals already converged, so these reads 
are stable).
+        List<List<Object>> sourceRows =
+                query(String.format(STABLE_QUERY, MYSQL_DATABASE, 
SOURCE_TABLE));
+        List<List<Object>> sinkRows =
+                query(String.format(STABLE_QUERY, MYSQL_DATABASE, 
SINK_TABLE_FILTER));
+        Assertions.assertEquals(
+                sourceRows.size(),
+                sinkRows.size(),
+                "sink row count must match source after the concurrent 
INSERT/UPDATE/DELETE");
+        // DELETE propagated: drop_columns_filter.sql runs `delete from 
products where id = 102`.
+        Assertions.assertTrue(
+                sinkRows.stream().noneMatch(row -> ((Number) 
row.get(0)).intValue() == 102),
+                "rows deleted at the source must also be deleted in the sink");
+        // INSERT propagated: id 110 is inserted by drop_columns_filter.sql.
+        Assertions.assertTrue(
+                sinkRows.stream().anyMatch(row -> ((Number) 
row.get(0)).intValue() == 110),
+                "rows inserted at the source must appear in the sink");
+        // UPDATE propagated: `set name='dailai' where id = 101`.
+        assertSinkNameEquals(sinkRows, 101, "dailai");
+
+        // the excluded drop.column must NOT have been applied to the sink 
schema
+        Assertions.assertTrue(
+                columnExists(MYSQL_DATABASE, SINK_TABLE_FILTER, "add_column1"),
+                "drop.column was excluded, so the sink must keep the column 
the source dropped");
+    }
+
+    /** Asserts the sink row with the given id exists and its {@code name} 
matches expectedName. */
+    private void assertSinkNameEquals(List<List<Object>> sinkRows, int id, 
Object expectedName) {
+        Optional<List<Object>> row =
+                sinkRows.stream().filter(r -> ((Number) r.get(0)).intValue() 
== id).findFirst();
+        Assertions.assertTrue(row.isPresent(), "expected sink row with id=" + 
id);
+        Assertions.assertEquals(
+                expectedName,
+                row.get().get(1),
+                "updated value must propagate to the sink for id=" + id);
+    }
+
+    private boolean columnExists(String database, String table, String column) 
{
+        return query(String.format(DESC, database, table)).stream()
+                .anyMatch(row -> 
column.equalsIgnoreCase(String.valueOf(row.get(0))));
+    }
+
     private void assertSchemaEvolution(String database, String sourceTable, 
String sinkTable) {
         await().atMost(30000, TimeUnit.MILLISECONDS)
                 .untilAsserted(
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/ddl/add_columns_filter.sql
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/ddl/add_columns_filter.sql
new file mode 100644
index 0000000000..814bcbf7af
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/ddl/add_columns_filter.sql
@@ -0,0 +1,24 @@
+--
+-- 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.
+--
+
+-- 
----------------------------------------------------------------------------------------------------------------
+-- DATABASE:  shop
+-- 
----------------------------------------------------------------------------------------------------------------
+CREATE DATABASE IF NOT EXISTS `shop`;
+use shop;
+
+alter table products add column add_column1 varchar(64) null;
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/ddl/drop_columns_filter.sql
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/ddl/drop_columns_filter.sql
new file mode 100644
index 0000000000..1376bedd1f
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/ddl/drop_columns_filter.sql
@@ -0,0 +1,29 @@
+--
+-- 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.
+--
+
+-- 
----------------------------------------------------------------------------------------------------------------
+-- DATABASE:  shop
+-- 
----------------------------------------------------------------------------------------------------------------
+CREATE DATABASE IF NOT EXISTS `shop`;
+use shop;
+
+alter table products drop column add_column1;
+insert into products
+values (110,"spare tire","24 inch spare tire",22.2),
+       (111,"new battery","12V battery",8.1);
+update products set name = 'dailai' where id = 101;
+delete from products where id = 102;
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/ddl/shop.sql
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/ddl/shop.sql
index 9887b4e687..bddc222fb5 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/ddl/shop.sql
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/ddl/shop.sql
@@ -46,6 +46,14 @@ CREATE TABLE if not exists 
mysql_cdc_e2e_sink_table_with_schema_change_exactly_o
  weight FLOAT
 );
 
+drop table if exists mysql_cdc_e2e_sink_table_schema_change_filter;
+CREATE TABLE if not exists mysql_cdc_e2e_sink_table_schema_change_filter (
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL DEFAULT 'SeaTunnel',
+ description VARCHAR(512),
+ weight FLOAT
+);
+
 ALTER TABLE products AUTO_INCREMENT = 101;
 
 INSERT INTO products
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/mysqlcdc_to_mysql_with_schema_change_filter.conf
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/mysqlcdc_to_mysql_with_schema_change_filter.conf
new file mode 100644
index 0000000000..828ef81cc2
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/resources/mysqlcdc_to_mysql_with_schema_change_filter.conf
@@ -0,0 +1,53 @@
+#
+# 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.
+#
+
+# Schema change event-type filtering (issue #11044):
+# add.column propagates, drop.column is excluded. Regression guard: a filtered
+# drop.column must NOT desync the produced row shape from the sink schema, so
+# subsequent data changes keep flowing instead of crashing the sink.
+
+env {
+  parallelism = 1
+  job.mode = "STREAMING"
+  checkpoint.interval = 5000
+}
+
+source {
+  MySQL-CDC {
+    server-id = 5680-5685
+    username = "st_user_source"
+    password = "mysqlpw"
+    table-names = ["shop.products"]
+    url = "jdbc:mysql://mysql_cdc_e2e:3306/shop"
+
+    schema-changes.enabled = true
+    schema-changes.exclude = ["drop.column"]
+  }
+}
+
+sink {
+  jdbc {
+    url = "jdbc:mysql://mysql_cdc_e2e:3306/shop"
+    driver = "com.mysql.cj.jdbc.Driver"
+    user = "st_user_sink"
+    password = "mysqlpw"
+    generate_sink_sql = true
+    database = shop
+    table = mysql_cdc_e2e_sink_table_schema_change_filter
+    primary_keys = ["id"]
+  }
+}

Reply via email to