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

morningman pushed a commit to branch branch-catalog-spi
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-catalog-spi by this 
push:
     new 778c5dd610f [P1-T03-T05] route plugin-driven scans first in nereids 
translator (#63641)
778c5dd610f is described below

commit 778c5dd610fb25336b1cad52297da7c61f400d43
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon May 25 11:45:04 2026 -0700

    [P1-T03-T05] route plugin-driven scans first in nereids translator (#63641)
    
    ## Summary
    
    P1 batch A — close out scan-node SPI consolidation while keeping
    migration-period fallbacks in place. Three surgical changes route
    `PluginDrivenExternalTable` first in the nereids translator hot paths so
    already-migrated SPI connectors (JDBC, ES) take the SPI route, while the
    existing `instanceof XExternalTable` chains remain as fallbacks for
    connectors still pending migration (P3–P7).
    
    - **T3** — `PhysicalPlanTranslator.visitPhysicalFileScan`: move the
    existing `PluginDrivenExternalTable` branch from position 8 to position
    1; the 7 connector-specific branches (HMS / Iceberg / Paimon / Trino /
    MaxCompute / LakeSoul / RemoteDoris) stay in place as migration-period
    fallbacks
    - **T4** — `PhysicalPlanTranslator.visitPhysicalHudiScan`: add a
    `PluginDrivenExternalTable` branch routed to
    `PluginDrivenScanNode.create(...)`, threading `tableSnapshot` +
    `scanParams` through `FileQueryScanNode` setters; `incrementalRelation`
    flagged as a P3 Hudi SPI extension TODO. The new branch is unreachable
    today (`PhysicalHudiScan` is only built for `HMSExternalTable +
    DLAType.HUDI`), so this is groundwork for P3 with zero current-day
    runtime impact
    - **T5** — `LogicalFileScan`: in `computeOutput()`, add a
    `PluginDrivenExternalTable` branch calling new helper
    `computePluginDrivenOutput()` — same shape as `computeIcebergOutput`,
    using `getFullSchema()` + virtualColumns; in
    `supportPruneNestedColumn()`, add an explicit `PluginDrivenExternalTable
    → false` branch. Both behaviorally equivalent for JDBC/ES today since
    they have no hidden cols and no virtualColumns
    
    P1 batch B (T1 — delete 13 legacy `Jdbc*Client` + `JdbcFieldSchema`) is
    deferred to P8 because the 3 fe-core callers —
    `PostgresResourceValidator`, `StreamingJobUtils`,
    `CdcStreamTableValuedFunction` — are live CDC streaming code that
    requires SPI extension for `getPrimaryKeys` / `getColumnsFromJdbc` /
    `listTables`, which is out of P1 surgical scope.
    
    Background and tracking docs live in `plan-doc/` (Master Plan §3.2 P1,
    tasks/P1-scan-node-cleanup.md, decisions log).
    
    ## Test plan
    
    - [x] `mvn -pl fe-core -am compile -Dmaven.build.cache.enabled=false` →
    BUILD SUCCESS
    - [x] `mvn -pl fe-core checkstyle:check` → 0 violations
    - [x] JDBC + ES regression-test passing — baseline established in P0 /
    PR #63582
    - [ ] PR CI green on this PR
    - [ ] Manual scan-node smoke for an SPI connector — JDBC `SELECT *`
    should fall into the new `PluginDrivenExternalTable` branch first
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 <[email protected]>
---
 .../glue/translator/PhysicalPlanTranslator.java    |  43 +++--
 .../trees/plans/logical/LogicalFileScan.java       |  25 +++
 plan-doc/HANDOFF.md                                | 198 ++++++++++++---------
 plan-doc/PROGRESS.md                               |  32 ++--
 plan-doc/tasks/P1-scan-node-cleanup.md             | 137 ++++++++++++++
 5 files changed, 326 insertions(+), 109 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
index 7b01ce6c6b1..1eaef6f98a7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
@@ -731,7 +731,16 @@ public class PhysicalPlanTranslator extends 
DefaultPlanVisitor<PlanFragment, Pla
         SessionVariable sv = ConnectContext.get().getSessionVariable();
         // TODO(cmy): determine the needCheckColumnPriv param
         ScanNode scanNode;
-        if (table instanceof HMSExternalTable) {
+        // Plugin-driven (SPI) tables are matched first; the connector-specific
+        // instanceof branches below are migration-period fallbacks that get 
removed
+        // as each connector lands on the SPI in P3-P7.
+        if (table instanceof PluginDrivenExternalTable) {
+            PluginDrivenExternalCatalog pluginCatalog =
+                    (PluginDrivenExternalCatalog) table.getCatalog();
+            scanNode = PluginDrivenScanNode.create(context.nextPlanNodeId(), 
tupleDescriptor,
+                    false, sv, context.getScanContext(), pluginCatalog,
+                    ((PluginDrivenExternalTable) table));
+        } else if (table instanceof HMSExternalTable) {
             if (directoryLister == null) {
                 this.directoryLister = new 
TransactionScopeCachingDirectoryListerFactory(
                         
Config.max_external_table_split_file_meta_cache_num).get(new 
FileSystemDirectoryLister());
@@ -779,12 +788,6 @@ public class PhysicalPlanTranslator extends 
DefaultPlanVisitor<PlanFragment, Pla
         } else if (table instanceof RemoteDorisExternalTable) {
             scanNode = new RemoteDorisScanNode(context.nextPlanNodeId(), 
tupleDescriptor, false, sv,
                     context.getScanContext());
-        } else if (table instanceof PluginDrivenExternalTable) {
-            PluginDrivenExternalCatalog pluginCatalog =
-                    (PluginDrivenExternalCatalog) table.getCatalog();
-            scanNode = PluginDrivenScanNode.create(context.nextPlanNodeId(), 
tupleDescriptor,
-                    false, sv, context.getScanContext(), pluginCatalog,
-                    ((PluginDrivenExternalTable) table));
         } else {
             throw new RuntimeException("do not support table type " + 
table.getType());
         }
@@ -819,19 +822,35 @@ public class PhysicalPlanTranslator extends 
DefaultPlanVisitor<PlanFragment, Pla
 
     @Override
     public PlanFragment visitPhysicalHudiScan(PhysicalHudiScan hudiScan, 
PlanTranslatorContext context) {
-        if (directoryLister == null) {
-            this.directoryLister = new 
TransactionScopeCachingDirectoryListerFactory(
-                    
Config.max_external_table_split_file_meta_cache_num).get(new 
FileSystemDirectoryLister());
-        }
         List<Slot> slots = hudiScan.getOutput();
         ExternalTable table = hudiScan.getTable();
         TupleDescriptor tupleDescriptor = generateTupleDesc(slots, table, 
context);
+        SessionVariable sv = ConnectContext.get().getSessionVariable();
+
+        // Plugin-driven (SPI) Hudi: route through PluginDrivenScanNode. 
Incremental scan
+        // (hudiScan.getIncrementalRelation) is not yet representable in the 
SPI; that
+        // gap is tracked for P3 when Hudi migrates to the connector framework.
+        if (table instanceof PluginDrivenExternalTable) {
+            PluginDrivenExternalCatalog pluginCatalog =
+                    (PluginDrivenExternalCatalog) table.getCatalog();
+            ScanNode scanNode = 
PluginDrivenScanNode.create(context.nextPlanNodeId(), tupleDescriptor,
+                    false, sv, context.getScanContext(), pluginCatalog,
+                    (PluginDrivenExternalTable) table);
+            FileQueryScanNode fileScan = (FileQueryScanNode) scanNode;
+            
hudiScan.getTableSnapshot().ifPresent(fileScan::setQueryTableSnapshot);
+            hudiScan.getScanParams().ifPresent(fileScan::setScanParams);
+            return getPlanFragmentForPhysicalFileScan(hudiScan, context, 
scanNode);
+        }
 
+        if (directoryLister == null) {
+            this.directoryLister = new 
TransactionScopeCachingDirectoryListerFactory(
+                    
Config.max_external_table_split_file_meta_cache_num).get(new 
FileSystemDirectoryLister());
+        }
         if (!(table instanceof HMSExternalTable) || ((HMSExternalTable) 
table).getDlaType() != DLAType.HUDI) {
             throw new RuntimeException("Invalid table type for Hudi scan: " + 
table.getType());
         }
         HudiScanNode hudiScanNode = new HudiScanNode(context.nextPlanNodeId(), 
tupleDescriptor, false,
-                hudiScan.getScanParams(), hudiScan.getIncrementalRelation(), 
ConnectContext.get().getSessionVariable(),
+                hudiScan.getScanParams(), hudiScan.getIncrementalRelation(), 
sv,
                 directoryLister, context.getScanContext());
         if (hudiScan.getTableSnapshot().isPresent()) {
             
hudiScanNode.setQueryTableSnapshot(hudiScan.getTableSnapshot().get());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
index f34ea0d633d..8ca7902a402 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
@@ -22,6 +22,7 @@ import org.apache.doris.analysis.TableSnapshot;
 import org.apache.doris.catalog.PartitionItem;
 import org.apache.doris.common.IdGenerator;
 import org.apache.doris.datasource.ExternalTable;
+import org.apache.doris.datasource.PluginDrivenExternalTable;
 import org.apache.doris.datasource.hive.HMSExternalTable;
 import org.apache.doris.datasource.iceberg.IcebergExternalTable;
 import org.apache.doris.datasource.iceberg.IcebergSysExternalTable;
@@ -203,6 +204,12 @@ public class LogicalFileScan extends 
LogicalCatalogRelation implements SupportPr
             return cachedOutputs.get();
         }
 
+        if (table instanceof PluginDrivenExternalTable) {
+            // SPI-driven tables: schema is fetched via 
ConnectorMetadata.getTableSchema()
+            // (see PluginDrivenExternalTable.initSchema). Use getFullSchema() 
so any
+            // hidden/metadata columns the connector exposes are reachable.
+            return computePluginDrivenOutput();
+        }
         if (table instanceof IcebergExternalTable) {
             // iceberg v3 need append row lineage columns
             return computeIcebergOutput((IcebergExternalTable) table);
@@ -225,6 +232,19 @@ public class LogicalFileScan extends 
LogicalCatalogRelation implements SupportPr
         return slots.build();
     }
 
+    private List<Slot> computePluginDrivenOutput() {
+        IdGenerator<ExprId> exprIdGenerator = 
StatementScopeIdGenerator.getExprIdGenerator();
+        Builder<Slot> slots = ImmutableList.builder();
+        table.getFullSchema()
+                .stream()
+                .map(col -> 
SlotReference.fromColumn(exprIdGenerator.getNextId(), table, col, qualified()))
+                .forEach(slots::add);
+        for (NamedExpression virtualColumn : virtualColumns) {
+            slots.add(virtualColumn.toSlot());
+        }
+        return slots.build();
+    }
+
     @Override
     public List<Slot> computeAsteriskOutput() {
         return super.computeAsteriskOutput();
@@ -233,6 +253,11 @@ public class LogicalFileScan extends 
LogicalCatalogRelation implements SupportPr
     @Override
     public boolean supportPruneNestedColumn() {
         ExternalTable table = getTable();
+        if (table instanceof PluginDrivenExternalTable) {
+            // No SPI capability for nested-column prune yet; default to off.
+            // Future ConnectorCapability flag will refine this.
+            return false;
+        }
         if (table instanceof IcebergExternalTable || table instanceof 
IcebergSysExternalTable) {
             return true;
         } else if (table instanceof HMSExternalTable) {
diff --git a/plan-doc/HANDOFF.md b/plan-doc/HANDOFF.md
index 9219adff17d..cdc3c4f7b74 100644
--- a/plan-doc/HANDOFF.md
+++ b/plan-doc/HANDOFF.md
@@ -8,139 +8,164 @@
 
 ## 📅 最后一次 handoff
 
-- **日期 / 时间**:2026-05-24(夜 ③)
+- **日期 / 时间**:2026-05-25(白天 ④)
 - **本 session 主导者**:Claude Opus 4.7(1M context)
-- **本 session 主题**:P0 批 2 守门 + 单测(T21-T23, T26-T27;T24-T25 转交用户在本地跑)—— **已 
commit**(用户人工 review 通过;hash 见 `git log --oneline -3`,subject 
`[feat](connector) add P0 batch 2 gate + unit tests (T21-T23, T26-T27)`)
-- **预估 context 使用**:~55%(健康)
+- **本 session 主题**:**P1 阶段关闭**(批 B = T1 推迟到 P8;in-scope 100% 完成)
+- **预估 context 使用**:~25%(健康;本场无编码,主要是 recon + 用户决议 + 跟踪文档同步)
 
 ---
 
 ## ✅ 本 session 完成项
 
-### 1. P0 批 2:守门 + 单测(T21-T23, T26-T27)
+### 1. 批 B (T1) recon — 揭示 callers 非 dead code
 
-| ID | 任务 | 文件 | 备注 |
-|---|---|---|---|
-| T21 ✅ | `tools/check-connector-imports.sh` | **新** 
`tools/check-connector-imports.sh` | grep 守门;接受可选 ROOT 参数;正负冒烟均通过 |
-| T22 ✅ | exec-maven-plugin 接入脚本 | edit `fe-connector/pom.xml` | 绑 `validate` 
阶段;`inherited=false`;用 `${project.basedir}/../../tools/...` 避开 `fe.dir` 解析时序 |
-| T23 ✅ | `FakeConnectorPlugin` + 默认行为测试 | **新** 
`fe-core/src/test/java/.../connector/fake/{FakeConnectorPlugin,FakeConnectorPluginTest}.java`
 | 11 个 @Test;零 override 的 `FakeMetadata` 验证所有 default 路径 |
-| T24 ⏳ | JDBC regression-test | — | **转交用户**在本地跑 |
-| T25 ⏳ | ES regression-test | — | **转交用户**在本地跑 |
-| T26 ✅ | `ExternalMetaCacheInvalidator` 路由测试 | **新** 
`fe-core/src/test/java/.../connector/ExternalMetaCacheInvalidatorTest.java` | 5 
个 @Test;`MockedStatic<Env>` + `mock(ExternalMetaCacheMgr)`;pin partition 
fallback & stats no-op |
-| T27 ✅ | converter 单测 | **新** 
`fe-core/src/test/java/.../connector/ddl/CreateTableInfoToConnectorRequestConverterTest.java`
 | 7 个 @Test;`mock(CreateTableInfo)` 绕开 18-arg ctor;4 partition style + 2 
bucket + 列穿透 |
+启动批 B 前对 `Jdbc*Client.java` + `JdbcFieldSchema.java` 的 fe-core 引用做了 Explore 
subagent 调研。结论:
 
-### 2. 验证
+| Caller(路径) | Live? | 用途 |
+|---|---|---|
+| `job/extensions/insert/streaming/PostgresResourceValidator.java` | ✅ 活 | 
CREATE JOB 时校验 PG 复制槽 / 发布;被 StreamingJobUtils → StreamingInsertJob → 
CreateJobCommand 链调用 |
+| `job/util/StreamingJobUtils.java` | ✅ 活 | `getJdbcClient()` + 
`getPrimaryKeys`/`getColumnsFromJdbc`/`getTablesNameList`,CDC 表枚举 + DDL 生成 |
+| `tablefunction/CdcStreamTableValuedFunction.java` | ✅ 活 | `cdc_stream` TVF,被 
`CdcStream.java:46` 调,streaming 作业执行链路 |
 
-- `tools/check-connector-imports.sh` 正/负冒烟测试通过
-- `mvn -pl fe-connector validate -Dmaven.build.cache.enabled=false` → **BUILD 
SUCCESS**(exec-maven-plugin 调起脚本)
-- `mvn -pl fe-core -am test 
-Dtest='FakeConnectorPluginTest,ExternalMetaCacheInvalidatorTest,CreateTableInfoToConnectorRequestConverterTest,ConnectorPluginManagerTest,ConnectorSessionImplTest'
 -DfailIfNoTests=false -Dmaven.build.cache.enabled=false` → **39/39 tests 
green**
-- `mvn -pl fe-core checkstyle:check` → **0 violations**
+测试侧:`StreamingJobUtilsTest`(需重写);`JdbcFieldSchemaTest` / 
`JdbcClickHouseClientTest` / `JdbcClientExceptionTest`(测 legacy 本身,随源删除)。
 
-### 3. 文档同步(§5.1 五步纪律)
+fe-connector 侧 SPI 替换 
`Jdbc*ConnectorClient`(ClickHouse/DB2/MySQL/Oracle/PostgreSQL/SQLServer/SapHana/Gbase)已就位,但
 **fe-core 不能直接 import** —— 会破坏 `tools/check-connector-imports.sh` 守门。
 
-- ✅ `tasks/P0-spi-foundation.md`:T21-T23, T26-T27 状态翻 ✅;T24-T25 owner 改 @用户;新增 
2026-05-24(夜 ③)日志条目(含 4 项 trade-off 说明);顶部验收清单 5 项翻 [x]
-- ✅ `PROGRESS.md`:§一 P0 进度条 74% → 93%;§三 P0 表追加批 2 7 行;§四加 2026-05-24(夜 
③)条目;§七 session 状态滚动
-- ✅ 本 HANDOFF.md 覆写
-- N/A `connectors/<name>.md`(本场不属任何具体连接器)
-- N/A `decisions-log.md` / `deviations-log.md`(trade-off 都在 RFC §15 范围内,未升 DV)
+### 2. 用户决议(Q4):推迟 T1 到 P8 收尾
 
-### 4. Commit(用户人工 review 通过后)
+- 删 T1 需要在 `ConnectorPlugin`/`ConnectorMetadata` 上为 CDC use case 暴露 
`getPrimaryKeys` / `getColumnsFromJdbc` / `listTables` 新 capability — 是 SPI 
扩展工作,超出 Master Plan §3.2 P1 scope
+- 现状无 runtime 风险——legacy JDBC client 仍在原位,CDC 功能正常
+- 决策:T1 推迟到 P8 收尾,与 streaming CDC 重构一起做(避免 P1 阶段引入 1-2 天计划外 SPI 设计)
 
-- ✅ `[feat](connector) add P0 batch 2 gate + unit tests (T21-T23, 
T26-T27)`(hash 见 `git log --oneline -3`)
-- 9 files changed:1 个 pom edit(fe-connector)+ 5 个新文件(1 脚本 + 4 测试相关)+ 3 个 
plan-doc 更新
-- 工作树 clean
+P1 状态因此提前关闭:**in-scope (T3+T4+T5) 100% 完成;T1 推迟 P8;T2 推迟 P4/P5**。
+
+### 3. 跟踪文档同步
+
+- `tasks/P1-scan-node-cleanup.md`:元信息状态翻 ✅;验收标准重新对齐(标 🚫/[x]/🟡);任务表 T1 翻 🚫 + 
备注引用 Q4;新增 白天 ④ 阶段日志条目;当前阻塞项更新
+- `PROGRESS.md`:header 项目总进度 16% → 20%;§一 P1 → 100% ✅;§一 P2 → 🚧 准备启动;全局进度 8% → 
12%;§三 P1 表 header 改 "✅ 已完成",T1 行翻 🚫;§四加 白天 ④ 条目;§七 session 状态更新
+- `HANDOFF.md`(本文件):覆盖更新到 P1 阶段关闭状态
 
 ---
 
 ## 🚧 本 session 进行中 / 未完成
 
-- **T24/T25**:JDBC + ES regression-test 转交用户在本地跑(containers / docker 
在本地更稳)。任务状态保持 ⏳,owner 改为 @用户。完成后用户在 PROGRESS / tasks 上翻 ✅ 即可
-- **本 HANDOFF 在 commit 内**——内容写的是 post-commit 状态,与 batch 2 代码、plan-doc 更新一并 
commit。不需要后续 amend
+无编码工作。剩余动作:
+
+1. **commit 本场 plan-doc 改动** — 3 个文件(P1 task / PROGRESS / HANDOFF)
+2. **push `catalog-spi-02` 到 morningman fork**(**待用户授权**)— 含批 A commit 
`43a12a05ffe` + 本场 doc commit
+3. **`gh pr create --repo apache/doris --base branch-catalog-spi --head 
morningman:catalog-spi-02`**(**待用户授权**)
 
 ---
 
 ## 📝 关键认知 / 临时发现
 
-继承上版认知不变。**本场新增**:
+继承前一版认知。**本场新增**:
 
-1. **maven-enforcer-plugin 不能原生 exec shell**——RFC §15.4 原文写"挂到 maven enforcer 
plugin",但 enforcer 只有 `requireXxx` 系列 rule 和 `EvaluateBeanshell`,没有内置的 
shell-exec rule。要么写 Java 自定义 Rule 类(重)要么走 `EvaluateBeanshell`(不直观)。**最终选择 
`exec-maven-plugin`**——fe-common 已用它跑 make + protoc,零新依赖;脚本 non-zero exit 即触发 
`BUILD FAILURE`,效果等价
-2. **`directory-maven-plugin` 的 `fe.dir` 属性在 `validate` 阶段还没 set**——它绑 
`initialize` 阶段(晚于 validate)。第一次写 pom 用了 
`${doris.home}/tools/...`(`doris.home=${fe.dir}/../`),结果路径解析为字面值 
`${fe.dir}/..//tools/...`。改用 `${project.basedir}/../../tools/...`(fe-connector 
aggregator basedir → workspace root → tools)避开属性时序问题
-3. **exec-maven-plugin 在 aggregator pom 的继承默认是 `inherited=true`**——会让 11 个 
fe-connector-* 子模块每次都重跑同一份扫描。本场设 `inherited=false`,只在 aggregator 自身 lifecycle 
跑一次。Trade-off:dev 跑单个子模块 `mvn -pl fe-connector/fe-connector-iceberg compile` 
时不会自动触发守门,但顶层 `mvn install` 必扫
-4. **`ConnectorMetaInvalidator` 的方法名是 `invalidateAll()` 不是 
`invalidateCatalog()`**——第一稿测试写错卡了一次 test-compile。SPI 接口侧明确写 
`invalidateAll`("Invalidates the entire catalog's metadata caches"),fe-core 侧 
`ExternalMetaCacheInvalidator.invalidateAll() → 
mgr.invalidateCatalog(catalogId)` 这才是路由
-5. **`Mockito.mockStatic(Env.class)`** 模式在 fe-core 
已有先例(`BDBDebuggerTest:115`),mockito-inline 是 fe 顶层 pom 已声明的 test 
dep,新测试可以直接用,无需修改任何 pom
-6. **`Mockito.mock(CreateTableInfo.class)`** 比真正构造 18-arg `CreateTableInfo` 
更便捷——converter 只读 8 个 getter,全部 stub 即可。如未来 converter 用到更多 getter,在 `stubInfo` 
helper 加新 stub
-7. **`mvn -pl fe-core test` 不带 `-am` 失败**(缺 fe-grpc / fe-filesystem-* 等本地未 
install 的 SNAPSHOT)。本场所有 fe-core 测试运行都用 `mvn -pl fe-core -am test -Dtest=... 
-DfailIfNoTests=false 
-Dmaven.build.cache.enabled=false`。`-DfailIfNoTests=false` 是必须的——`-am` 会带上 
fe-foundation 等 upstream,它们没有匹配 `-Dtest=` 的测试就会爆 surefire 错
-8. **fe-connector 模块当前 import 现状**:`grep -rEn "^import org\.apache\.doris\." 
fe/fe-connector/*/src/main/java | awk` → 仅 4 个根包 `connector / extension / 
thrift / 
trinoconnector`。所有禁词包(catalog/common/datasource/qe/analysis/nereids/planner)都被守门,baseline
 已经合规
+1. **`tools/check-connector-imports.sh` 是一个隐含的设计约束** — fe-core 不能 import 
fe-connector 内部类(`org.apache.doris.connector.*`),所以"复用"SPI 实现唯一通道是 
`ConnectorPlugin` 接口。批 B 直接 import `JdbcConnectorClient` 替换 `JdbcClient` 
本能解法**走不通**——一定要经过 SPI capability 扩展。这条约束以前 P0 文档讲过,但批 B recon 时是第一次真正触发它
+2. **CDC streaming 是 SPI 未覆盖的 use case** — 现有 SPI(ConnectorMetadata.getTable / 
listTables / getTableHandle)是面向"标准 SELECT"的,没暴露 PK 
探测、columns-from-jdbc-driver、replication-slot 校验。P8 启动前需要先在 RFC 中起 §17 
章节描述这套扩展,否则 P8 实施会 stall
+3. **fe-connector 侧的 `Jdbc*ConnectorClient` 是 P0 阶段 JDBC 迁移的产物** — 它们没有暴露 PK / 
column-from-driver 接口(按 ConnectorMetadata 标准抽象设计),所以即便允许 fe-core 直接 import 
也不能直接替换 legacy client。换言之 SPI 设计本身需要扩展(不只是 "改 import 路径")
 
 ---
 
 ## 🎯 下一个 session 第一件事
 
-### Track A:等 T24/T25 收尾
+> P1 已关闭。下一阶段 P2 (trino-connector,2 周)。**预备动作**:先把批 A push + PR,再做 P2 recon。
 
 ```
-1. 用户跑完 JDBC + ES regression-test 后
-2. tasks/P0-spi-foundation.md 把 T24/T25 翻 ✅
-3. PROGRESS.md 进度条 93% → 100%;状态 🚧 → ✅
-4. 写 P0 阶段收尾 commit(如果 T24/T25 有微调代码)
+1. git branch --show-current → 确认在 catalog-spi-02
+   git status → 应 clean(本场 doc commit 已 push 前提下)
+   git log --oneline -3 → 应见 2 个本地未推 commit:
+     a) 批 A scan-node 收口(43a12a05ffe)
+     b) P1 关闭 + T1 推迟 P8 doc commit
+2. 读 PROGRESS.md + 本 HANDOFF + tasks/P1-scan-node-cleanup.md(确认 P1 已 ✅)
+3. push + PR(如本场尚未完成):
+   git push -u origin catalog-spi-02
+   gh pr create --repo apache/doris --base branch-catalog-spi \
+     --head morningman:catalog-spi-02 \
+     --title "[P1-T03-T05] route plugin-driven scans first in nereids 
translator"
+4. 启动 P2 (trino-connector) recon — 用 Explore subagent:
+   a. fe-core 侧 `datasource/trinoconnector/` 现状(多少类、多少 LOC)
+   b. fe-connector 侧 trino-connector 模块完成度(连接器看板里目前标 70%)
+   c. SPI_READY 加进 `CatalogFactory.SPI_READY_TYPES` 的预条件
+   d. 反向 instanceof:grep "instanceof.*Trino" in nereids/planner(看板里目前标 0/2)
+5. 创建 plan-doc/tasks/P2-trino-connector-migration.md(_template.md 复制)
+6. 守门:P2 改动跨 fe-core + fe-connector 双侧,每次 commit 前
+   - `mvn -pl fe-connector validate` 触发 check-connector-imports.sh
+   - `mvn -pl fe-core checkstyle:check`
 ```
 
-### Track B:选 P0 末加项 vs 直接进 P1
+---
+
+## ⚠️ 开放问题 / 风险提示
 
-- **选项 B1**:P0-T28 benchmark(R-006 缓解,1k catalog × `listTableNames` 性能基线)。原列入 
P1,可前置到 P0 末加,让 P0 出阶段干净
-- **选项 B2**:直接进 P1(scan-node 收口 + 重复清理)。P0 既然 93% 接近收尾,T24/T25 跑完即关阶段
-- 推荐 B2(B1 在 P1 阶段开题更自然,benchmark 跟 scan-node 工作正好同期)
+继承前一版;批 B 关闭 1 项、转入 P8 待办 1 项;其余沿用。
 
-### ~~Track C:commit 批 2~~(已收尾)
+### 本场关闭
 
-批 2 已合入 `catalog-spi-00`;无需再开 Track C。
+- ~~T1 何时实施~~ — 已决:推迟 P8 收尾
 
----
+### 本场新增(P8 待办)
 
-## ⚠️ 开放问题 / 风险提示
+1. **P8 SPI 扩展:CDC capability 群**:为 streaming CDC 在 SPI 上暴露 `getPrimaryKeys` / 
`getColumnsFromJdbc` / `listTables`(候选:`ConnectorMetadata` 新 default 方法 + 或 
`ConnectorPlugin` 上的 `Optional<ConnectorCdcSupport>`);改写 
PostgresResourceValidator / StreamingJobUtils / CdcStreamTableValuedFunction 走 
SPI;重写 StreamingJobUtilsTest;批量删 13 个 Jdbc*Client + JdbcFieldSchema + 3 个 
legacy test。**预估**:~1-2 天 SPI 设计 + ~1 天实施
+2. **P8 启动前 RFC 扩展**:在 `01-spi-extensions-rfc.md` 新增 §17 章节描述 CDC capability 
设计;否则 P8 实施会 stall
 
-继承上版 7 项不变(删了"未 commit batch 1"项;增加本场 trade-off):
-
-1. **守门挂 `exec-maven-plugin` 而非 `maven-enforcer-plugin`**:RFC §15.4 
原文写后者。本场用前者(等价实现,0 新依赖)。是否在 RFC §15.4 加脚注说明这个偏差?**判断**:trade-off 在 RFC 范围内,不升 
DV;若有 reviewer 强烈要求 enforcer 写 Java Rule 类再重做
-2. **守门 `inherited=false`**:dev 跑单连接器 `mvn -pl 
fe-connector/fe-connector-iceberg compile` 时不会触发。是否要改 
`inherited=true`?**判断**:现状没人手动跑这条命令日常迭代,重复扫的成本(11 × ~50ms)也不大;如未来某个连接器开发体感差再改
-3. **`invalidatePartition` 测试 pin 当前 fallback**:一旦 SPI 在该方法签名上加 column 
名携带能力,bridge 和测试必须同步更新。测试已留 inline comment 描述意图
-4. **`CreateTableInfo` 用 mock**:converter 改用 mock 之外的 getter 时,需在 `stubInfo` 
helper 加新 stub。Trade-off:测试更聚焦但代价是输入对象不"真实"
-5. **partition 风格的 IDENTITY vs TRANSFORM 判别**:测试覆盖了"全 UnboundSlot → 
IDENTITY"和"含 UnboundFunction → TRANSFORM"两路径,但没覆盖"UnboundSlot + UnboundFunction 
混合"——按 converter 当前实现,只要有任意一个 UnboundFunction 就走 TRANSFORM 路径,UnboundSlot 在 
`convertFields()` 里也会被识别为 `identity` transform。这个混合场景的语义是否符合预期?**判断**:RFC §4.2 
未明确混合用法,留待 P5/P6 Iceberg 真正用到时评估
-6. (沿用)`ColumnDefinition.defaultValue` SPI 缺位
-7. (沿用)LIST/RANGE `initialValues` flatten 缺位
-8. (沿用)`PluginDrivenExternalCatalog.createTable` 返回值丢失"已存在"信息
-9. (沿用)bucket 算法名 `"doris_default"` / `"doris_random"` 占位
-10. (沿用)Maven build cache 误导问题;`mvn -pl fe-core` 必须 cwd=`fe/`
-11. (沿用)`PluginDrivenTransactionManager.begin(ConnectorTransaction)` 暂无 caller
-12. (沿用)`invalidatePartition` fallback;`invalidateStatistics` no-op
-13. (沿用,本场强化)**`mvn -pl fe-core test` 不带 `-am` 失败**:必须 `-am 
-DfailIfNoTests=false`
+### 沿用(保留)
+
+3. **T4 PluginDrivenScanNode 不支持 hudi 增量场景** — `incrementalRelation` 待 P3 Hudi 
迁移时 SPI 扩展
+4. **T2 已推迟到 P4/P5**(用户决议 Q2,2026-05-25)
+5. **T3 fallback 保留期跨度长**(P1 → P7 20 周)—— 每连接器在 P3-P7 迁移完成后立即删对应 fallback
+6. (沿用 P0)`ColumnDefinition.defaultValue` SPI 缺位 — P5/P6 评估
+7. (沿用 P0)LIST/RANGE `initialValues` flatten 缺位 — P5/P6 评估
+8. (沿用 P0)`PluginDrivenExternalCatalog.createTable` 返回值丢失"已存在"信息 — P5/P6/P7 评估
+9. (沿用 P0)bucket 算法名 `"doris_default"` / `"doris_random"` 占位 — Hive/Iceberg 
自己推导
+10. (沿用 P0)Maven build cache 误导;`mvn -pl fe-core` 必须 cwd=`fe/` + 
`-am`;`-Dtest=` 务必带 `-DfailIfNoTests=false`
+11. (沿用 P0)`PluginDrivenTransactionManager.begin(ConnectorTransaction)` 暂无 
caller — P5/P6/P7 接通
+12. (沿用 P0)`ConnectorMetaInvalidator.invalidatePartition` fallback 到 
invalidateTable;`invalidateStatistics` no-op
+13. (沿用 P0)`mvn -pl fe-core test` 不带 `-am` 失败
 
 ---
 
 ## 📂 当前关键文件清单
 
-### 本场新增 / 修改(已 commit)
+### 本场(2026-05-25 白天 ④)修改
+
+```
+MOD  plan-doc/tasks/P1-scan-node-cleanup.md   (元信息 ✅;验收标准重对齐;T1 → 🚫;新增白天 ④ 日志)
+MOD  plan-doc/PROGRESS.md                     (P1 → 100% ✅;P2 → 准备启动;§三 T1 翻 
🚫;§四加白天 ④)
+MOD  plan-doc/HANDOFF.md                      (本文件覆盖更新)
+```
+
+工作树状态(本场 commit 前):
+```
+ M plan-doc/tasks/P1-scan-node-cleanup.md
+ M plan-doc/PROGRESS.md
+ M plan-doc/HANDOFF.md
+```
+
+### 待 push 的本地 commit(catalog-spi-02 → upstream-apache/branch-catalog-spi)
+
+```
+43a12a05ffe  [refactor](connector) [P1-T03-T05] route plugin-driven scans 
first in nereids translator
+???????????  [doc](connector) [P1] close P1 — defer T1 to P8, batch A only     
 ← 本场即将创建
+```
+
+### P2 (trino-connector) 涉及的目标(recon 时确认)
 
 ```
-NEW  tools/check-connector-imports.sh                                          
  (gate script)
-MOD  fe/fe-connector/pom.xml                                                   
  (exec-maven-plugin)
-NEW  
fe/fe-core/src/test/java/org/apache/doris/connector/fake/FakeConnectorPlugin.java
-NEW  
fe/fe-core/src/test/java/org/apache/doris/connector/fake/FakeConnectorPluginTest.java
        (11 tests)
-NEW  
fe/fe-core/src/test/java/org/apache/doris/connector/ExternalMetaCacheInvalidatorTest.java
    (5 tests)
-NEW  
fe/fe-core/src/test/java/org/apache/doris/connector/ddl/CreateTableInfoToConnectorRequestConverterTest.java
  (7 tests)
-MOD  plan-doc/PROGRESS.md
-MOD  plan-doc/tasks/P0-spi-foundation.md
-MOD  plan-doc/HANDOFF.md(本文件)
+fe/fe-core/src/main/java/org/apache/doris/datasource/trinoconnector/   (待 
recon — 看现状)
+fe/fe-connector/fe-connector-trino-connector/                          
(已存在;看板里标 70%)
+nereids/glue/translator/PhysicalPlanTranslator.java                    (T3 
fallback 待 P2 完成时清理 trino 分支)
+CatalogFactory.SPI_READY_TYPES                                          (P2 末加 
"trino-connector" 进白名单)
 ```
 
 ### 跟踪体系(沿用不变)
 
 ```
-plan-doc/  (~225K, 17 文件)
+plan-doc/  (~225K, 18 文件)
 ├── 00-connector-migration-master-plan.md / 01-spi-extensions-rfc.md
 ├── README.md / PROGRESS.md / AGENT-PLAYBOOK.md / HANDOFF.md
 ├── decisions-log.md (18) / deviations-log.md (0) / risks.md (14)
-├── tasks/{_template.md, P0-spi-foundation.md}
+├── tasks/{_template.md, P0-spi-foundation.md, P1-scan-node-cleanup.md}
 └── connectors/{_template.md, jdbc, es, trino-connector, hudi, maxcompute, 
paimon, iceberg, hive}.md
 ```
 
@@ -148,11 +173,10 @@ plan-doc/  (~225K, 17 文件)
 
 ## 🧠 给下一个 agent 的 meta 建议
 
-- **当前分支是 `catalog-spi-00`**。新 session 开场 `git branch --show-current` 确认
-- **批 2(T21-T23, T26-T27)已合入 `catalog-spi-00`**(subject `[feat](connector) add 
P0 batch 2 gate + unit tests (T21-T23, T26-T27)`),无需 review 老代码;直接读最新源即可。如果对 6 
个新/改文件有调整建议,走 DV 流程登记后再改,不要 silent edit
-- **T24/T25 owner 是用户**,不要自己尝试跑 docker regression-test
-- **Maven build 的 cwd 必须是 `fe/`**,不是 workspace 根;`mvn -pl fe-core` 需要 `-am`;运行 
`-Dtest=` 时务必带 `-DfailIfNoTests=false`,否则 upstream 模块(fe-foundation 等)找不到匹配 
test 会爆 surefire 错
-- 本场没产生新 decision / deviation——所有 trade-off 在 RFC §15 范围内,由代码注释 + 本 HANDOFF 
"开放问题" 列出
-- 本场用 `Mockito.mockStatic` + `Mockito.mock(CreateTableInfo)` 两个套路绕开了重度 fe-core 
bootstrap——批 1 的 `CreateTableInfoToConnectorRequestConverter` 同样可以这样测,套路通用。后续 
P1/P2 写 unit-test 可以复用
-- **必读 AGENT-PLAYBOOK §六 anti-patterns** 再开始动手
-- **本 HANDOFF 不内嵌 commit hash**——hash 通过 `git log --grep="P0 batch 2"` 或 `git 
log --oneline -3` 定位。本场无 amend,HANDOFF 与代码同 commit 落盘
+- **分支 `catalog-spi-02`**:本场结束时含 2 个本地未推 commit(批 A scan-node + P1 关闭 
doc)。push 与 PR 创建是**风险动作**,必须先与用户确认(已在本场末尾问过;如本场已 push,下场看 `git log --oneline 
-3` 验证 `origin/catalog-spi-02` 同步)
+- **PR 目标分支永远是 `apache/doris:branch-catalog-spi`**(不是 master)
+- **commit message** 沿用 `[refactor|feat|doc](connector) [Pn-Tnn] ...` 
前缀风格(AGENT-PLAYBOOK §5.4)
+- **Maven 命令**:cwd=`fe/`;`mvn -pl fe-core -am compile 
-Dmaven.build.cache.enabled=false`;测试用 `-Dtest=... -DfailIfNoTests=false`
+- **P2 启动前必读**:`connectors/trino-connector.md`(连接器看板里目前 70% 完成度)+ Master Plan 
§3.3 P2 章节
+- **P2 主要工作量预估**:补齐 fe-connector trino-connector 模块剩余 30%(核心是 catalog 注册 + 
SPI_READY_TYPES);删 fe-core 侧 trino-connector legacy;清掉 T3 fallback 中的 trino 
分支(PhysicalPlanTranslator)
+- **不要试图删 13 个 Jdbc*Client** — P1 阶段已决议推迟到 P8。看到 legacy jdbc client 不要技痒
diff --git a/plan-doc/PROGRESS.md b/plan-doc/PROGRESS.md
index b41dc3f4585..518a1cd8cf2 100644
--- a/plan-doc/PROGRESS.md
+++ b/plan-doc/PROGRESS.md
@@ -1,6 +1,6 @@
 # 📊 项目进度仪表盘
 
-> 最后更新:**2026-05-24(夜 ③)** | 当前阶段:**P0 SPI 缺口补齐**(批 0 + 批 1 + 批 2 代码侧完成;待 
T24-T25 用户跑 JDBC/ES regression-test) | 项目总进度:**13%**
+> 最后更新:**2026-05-25** | 当前阶段:**P1 已收口**(in-scope T3+T4+T5 完成;T1 推迟 P8、T2 推迟 
P4/P5;待 batch A push + PR)→ **P2 trino-connector 准备启动** | 项目总进度:**20%**
 > [README](./README.md) · [Master 
 > Plan](./00-connector-migration-master-plan.md) · [SPI 
 > RFC](./01-spi-extensions-rfc.md) · [Decisions](./decisions-log.md) · 
 > [Deviations](./deviations-log.md) · [Risks](./risks.md) · [Agent 
 > Playbook](./AGENT-PLAYBOOK.md) · [Handoff](./HANDOFF.md)
 
 ---
@@ -9,9 +9,9 @@
 
 | 阶段 | 范围 | 估时 | 进度 | 状态 | 任务文档 |
 |---|---|---|---|---|---|
-| **P0** | SPI 缺口补齐 | 2 周 | ▰▰▰▰▰▰▰▰▰▱ 93% | 🚧 收尾(批 0 + 1 + 2 代码侧完成 T03-T23, 
T26-T27;T24-T25 用户在本地跑 regression-test) | 
[tasks/P0](./tasks/P0-spi-foundation.md) |
-| P1 | scan-node 收口 + 重复清理 | 1 周 | ▱▱▱▱▱▱▱▱▱▱ 0% | ⏸ 待启动(被 P0 阻塞)| — |
-| P2 | trino-connector 迁移 | 2 周 | ▱▱▱▱▱▱▱▱▱▱ 0% | ⏸ 待启动 | — |
+| **P0** | SPI 缺口补齐 | 2 周 | ▰▰▰▰▰▰▰▰▰▰ 100% | ✅ 完成(PR #63582 squash-merge 
`c6f056fa5bd`,T24-T25 流水线全绿)| [tasks/P0](./tasks/P0-spi-foundation.md) |
+| **P1** | scan-node 收口 + 重复清理 | 1 周 | ▰▰▰▰▰▰▰▰▰▰ 100% | ✅ 完成(in-scope 
T3+T4+T5 ✅;T1 推迟 P8;T2 推迟 P4/P5;commit `43a12a05ffe` 待 push + PR)| 
[tasks/P1](./tasks/P1-scan-node-cleanup.md) |
+| **P2** | trino-connector 迁移 | 2 周 | ▱▱▱▱▱▱▱▱▱▱ 0% | 🚧 准备启动 | — |
 | P3 | hudi 迁移 | 2 周 | ▱▱▱▱▱▱▱▱▱▱ 0% | ⏸ 待启动 | — |
 | P4 | maxcompute 迁移 | 2 周 | ▱▱▱▱▱▱▱▱▱▱ 0% | ⏸ 待启动 | — |
 | P5 | paimon 迁移 | 3 周 | ▱▱▱▱▱▱▱▱▱▱ 0% | ⏸ 待启动 | — |
@@ -19,7 +19,7 @@
 | P7 | hive (+HMS) 迁移 | 6 周 | ▱▱▱▱▱▱▱▱▱▱ 0% | ⏸ 待启动 | — |
 | P8 | 收尾清理 | 2 周 | ▱▱▱▱▱▱▱▱▱▱ 0% | ⏸ 待启动 | — |
 
-**全局进度:7%**(25 周计划中处于第 1 周末)
+**全局进度:12%**(25 周计划中 P0+P1 共 3 周完成)
 
 ---
 
@@ -44,7 +44,16 @@
 
 > 状态非 ✅ 的项,按阶段聚合。详细见各阶段 task 文件。
 
-### P0 — SPI 缺口补齐
+### P1 — scan-node 收口 + 重复清理(✅ 已完成)
+| ID | Task | 批次 | Owner | 状态 | 启动 | 备注 |
+|---|---|---|---|---|---|---|
+| P1-T03 | `PhysicalPlanTranslator.visitPhysicalFileScan` 收口(保留 fallback) | 批 
A | @me | ✅ | 2026-05-25 | `PluginDrivenExternalTable` 分支已前置;7 个老分支保留 |
+| P1-T04 | `visitPhysicalHudiScan` 委托给 `PluginDrivenScanNode` | 批 A | @me | ✅ 
| 2026-05-25 | SPI 分支已加;`incrementalRelation` 待 P3 SPI 扩展 |
+| P1-T05 | `LogicalFileScan.computeOutput` 改走 SPI | 批 A | @me | ✅ | 2026-05-25 
| `computePluginDrivenOutput` + `supportPruneNestedColumn` 显式分支 |
+| P1-T01 | 删除 13 个 `Jdbc*Client.java` + `JdbcFieldSchema.java` | 🚫 推迟 P8 | — | 
🚫 | — | 2026-05-25 决议(Q4):3 个 fe-core caller 是活的 CDC streaming 代码,删除需 SPI 扩展,P8 
收尾时一并做 |
+| P1-T02 | 重复 PaimonPredicateConverter + McStructureHelper 处理 | 🚫 推迟 P4/P5 | — 
| 🚫 | — | 用户决议 Q2(2026-05-25) |
+
+### P0 — SPI 缺口补齐(✅ 已完成)
 | ID | Task | Owner | 状态 | 启动 | 备注 |
 |---|---|---|---|---|---|
 | P0-T01 | RFC §16.2 决策点闭环 | @me | ✅ | 2026-05-24 | 全部 18 条决策已敲定 |
@@ -72,8 +81,8 @@
 | P0-T21 | `tools/check-connector-imports.sh` 实现 | @me | ✅ | 2026-05-24 | grep 
守门;正/负冒烟均通过 |
 | P0-T22 | exec-maven-plugin 接入脚本(fe-connector aggregator validate) | @me | ✅ 
| 2026-05-24 | `inherited=false`;RFC §15.4 等价实现 |
 | P0-T23 | `FakeConnectorPlugin` + 11 个 default 行为测试 | @me | ✅ | 2026-05-24 | 
覆盖 Connector/Metadata/TableOps/WriteOps/Session/Context 全 default |
-| P0-T24 | JDBC regression-test 全套跑通 | @用户 | ⏳ | — | 用户在本地跑 |
-| P0-T25 | ES regression-test 全套跑通 | @用户 | ⏳ | — | 用户在本地跑 |
+| P0-T24 | JDBC regression-test 全套跑通 | @用户 | ✅ | 2026-05-25 | PR #63582 流水线绿 |
+| P0-T25 | ES regression-test 全套跑通 | @用户 | ✅ | 2026-05-25 | PR #63582 流水线绿 |
 | P0-T26 | `ConnectorMetaInvalidator` 路由测试 | @me | ✅ | 2026-05-24 | 5 个 
@Test;MockedStatic&lt;Env&gt; |
 | P0-T27 | `CreateTableInfoToConnectorRequestConverter` 单元测试 | @me | ✅ | 
2026-05-24 | 7 个 @Test;4 partition style + 2 bucket |
 
@@ -85,6 +94,9 @@
 
 > 倒序,新内容置顶;超过 14 天的条目移除(git log 保留历史)。
 
+- **2026-05-25(白天 ④)** ✅ **P1 阶段关闭**:批 B (T1) recon 揭示 3 个 fe-core JDBC client 
caller(PostgresResourceValidator / StreamingJobUtils / 
CdcStreamTableValuedFunction)均为活的 CDC streaming 代码(非 dead code),删除需要在 
ConnectorPlugin/ConnectorMetadata 上为 CDC 暴露新 capability(getPrimaryKeys / 
getColumnsFromJdbc / listTables)。用户决议(Q4):**推迟 T1 到 P8 收尾**(与 streaming CDC 
重构一起做)。P1 in-scope(T3+T4+T5)100% 完成;剩余动作:batch A push + PR
+- **2026-05-25(白天 ③)** ✅ **P1 批 A 完成**(T03+T04+T05 scan-node SPI 
收口):`PhysicalPlanTranslator.visitPhysicalFileScan` `PluginDrivenExternalTable` 
分支前置(T3);`visitPhysicalHudiScan` 加 SPI 分支并通过 `FileQueryScanNode` setters 透传 
`scanParams`/`tableSnapshot`,`incrementalRelation` 记 P3 
TODO(T4);`LogicalFileScan.computeOutput` 新增 `computePluginDrivenOutput()` 
helper + 显式 `supportPruneNestedColumn → false` 分支(T5)。fe-core BUILD SUCCESS + 
checkstyle 0;对当前 SPI 表(JDBC/ES)行为等价;7 个连接器特定分支原地保留作 P3-P7 fallback
+- **2026-05-25** ✅ **P0 全阶段完成**:PR 
[#63582](https://github.com/apache/doris/pull/63582) squash-merge 到 
`apache/doris:branch-catalog-spi`(hash `c6f056fa5bd`);T24/T25 流水线全绿;P0 阶段进度 
100%。新本地分支 `catalog-spi-02` 基于最新 base 创建,**P1 启动**(scan-node 收口 + 重复清理,1 周)
 - **2026-05-24(夜 ③)** ✅ **P0 批 2 守门 + 单测完成**(T21-T23, T26-T27;T24-T25 用户跑):新增 
`tools/check-connector-imports.sh` grep 守门 + 通过 exec-maven-plugin 在 
`fe-connector` aggregator validate 阶段调起(`inherited=false`);新增 
`FakeConnectorPlugin`(fe-core test)+ 23 个新 @Test 覆盖 11 个 default 路径 + 
ConnectorMetaInvalidator 5 个 routing + Converter 7 个(4 partition style × 
IDENTITY/TRANSFORM/LIST/RANGE + hash/random bucket + 列穿透);39/39 tests 
green;checkstyle 0;JDBC/ES regression-test 转交用户在本地执行
 - **2026-05-24(夜 ②)** ✅ **P0 批 1 DDL + Partition SPI 完成**(T13-T20):新增 
`connector.api.ddl` 包 5 个 POJO(CreateTableRequest + 4 spec);`ConnectorTableOps` 
加 4 个 default(createTable(request) + 
listPartitionNames/listPartitions/listPartitionValues);`ConnectorPartitionInfo` 
追加 rowCount/sizeBytes/lastModifiedMillis;fe-core 新 
`CreateTableInfoToConnectorRequestConverter` 覆盖 IDENTITY/TRANSFORM/LIST/RANGE 
四种 partition + hash/random bucket;`PluginDrivenExternalCatalog.createTable` 路由到 
SPI;fe-core BUIL [...]
 - **2026-05-24(深夜)** ✅ **P0 批 0 fe-core 
桥接完成**(T09-T12):`ExternalMetaCacheInvalidator` + `ConnectorMvccSnapshotAdapter` 
新类、`DefaultConnectorContext.getMetaInvalidator()` 
override、`PluginDrivenTransactionManager` 加 SPI `ConnectorTransaction` 
重载(legacy auto-commit 不变);fe-core 全编译通过 + checkstyle 0 violations;JDBC/ES 下游 
zero-impact
@@ -129,8 +141,8 @@
 
 > 当本项目通过 Claude Code 这类 LLM agent 推进时,跟踪当前 session 状态、handoff 状况和 context 健康度。
 
-- **本 session 已完成**:P0 批 2 守门 + 单测(T21-T23, T26-T27)—— 1 
个新脚本(`tools/check-connector-imports.sh`)+ 1 个 fe-connector aggregator pom 加 
exec-maven-plugin + 4 个 fe-core test 新文件(`FakeConnectorPlugin` + 3 个 
*Test);39/39 tests green;checkstyle 0;T24/T25 转交用户在本地跑 JDBC/ES regression-test
-- **下一个 session 应做**:等 T24/T25 用户跑完后翻 ✅ → P0 阶段全收尾 → 启动 P1(scan-node 
收口);或在等待期间开 P0-T28 benchmark(R-006 缓解,原列入 P1)作为 P0 末加项
+- **本 session 已完成**:P1 批 A (T3+T4+T5) commit `43a12a05ffe`(local,未 push)→ 批 B 
(T1) recon 揭示 callers 非 dead code → 用户决议 T1 推迟 P8 → P1 阶段关闭 → 跟踪文档(P1 task / 
PROGRESS / HANDOFF)全部同步
+- **下一个 session 应做**:(1)push `catalog-spi-02` 到 morningman fork;(2)`gh pr 
create --repo apache/doris --base branch-catalog-spi --head 
morningman:catalog-spi-02`;(3)启动 P2 (trino-connector) recon
 - **是否需要 handoff**:是,已写新 [HANDOFF.md](./HANDOFF.md)
 - **协作规范**:[AGENT-PLAYBOOK.md](./AGENT-PLAYBOOK.md)(context 预算、subagent 
使用、handoff 触发条件)
 
diff --git a/plan-doc/tasks/P1-scan-node-cleanup.md 
b/plan-doc/tasks/P1-scan-node-cleanup.md
new file mode 100644
index 00000000000..d2a9521d0ad
--- /dev/null
+++ b/plan-doc/tasks/P1-scan-node-cleanup.md
@@ -0,0 +1,137 @@
+# P1 — scan-node 收口 + 重复清理
+
+> 阶段总览见 [00-master-plan §3.2](../00-connector-migration-master-plan.md)。
+> 协作规范见 [AGENT-PLAYBOOK.md](../AGENT-PLAYBOOK.md)。
+
+---
+
+## 元信息
+
+- **状态**:✅ 完成(in-scope: T3+T4+T5;T1 推迟 P8;T2 推迟 P4/P5)
+- **启动日期**:2026-05-25
+- **目标完成**:2026-06-01(1 周)
+- **实际完成**:2026-05-25(提前;scope 大幅收窄)
+- **阻塞**:无
+- **阻塞下游**:P2 (trino-connector) 可启动;批 A scan-node 收口已就位
+- **主 owner**:@me
+- **分支**:`catalog-spi-02`(基于 `upstream-apache/branch-catalog-spi`;批 A 已 commit 
43a12a05ffe,待 push + PR)
+
+---
+
+## 阶段目标
+
+承接 P0 的 SPI baseline,做两件事:
+
+1. **删旧**:清理 fe-core 中已经被 SPI 实现覆盖、但还没删的 legacy 代码(JDBC 旧 
client、Paimon/MaxCompute 重复 converter)。
+2. **收口**:把 `PhysicalPlanTranslator.visitPhysicalFileScan` 的 7+ 个 `instanceof 
XExternalTable` 分支统一到 `PluginDrivenExternalTable` 路径(迁移期可保留老分支兜底);让 
`LogicalFileScan.computeOutput` 通过 SPI 而非 instanceof 拿 metadata 列。
+
+完成后:
+
+- `PhysicalPlanTranslator` 不再 `import` 任何具体 `*ExternalTable` 类(除迁移期 fallback)。
+- 后续每个连接器迁移(P3-P7)只需删掉对应 fallback 分支,不需要触碰 scan-node 主干。
+
+---
+
+## 验收标准
+
+从 master plan §3.2 同步(**两项推迟**已在状态前置标注):
+
+- 🚫 ~~13 个 `datasource/jdbc/client/Jdbc*Client.java` + `JdbcFieldSchema.java` 
全部删除~~ — **推迟到 P8**(2026-05-25 决议:3 个 fe-core caller 是活的 CDC streaming 代码,删除需 
SPI 扩展,不属 P1 surgical scope。详见任务清单 T1 备注)
+- 🚫 ~~fe-core 重复的 `PaimonPredicateConverter` + `McStructureHelper` 处理完毕~~ — 
**推迟到 P4/P5**(用户决议 Q2,2026-05-25)
+- [x] `PhysicalPlanTranslator.visitPhysicalFileScan` 优先走 
`PluginDrivenExternalTable` 分支 — 批 A T3
+- [x] `visitPhysicalHudiScan` 通过 `PluginDrivenScanNode` 处理增量场景(分支已就位,P3 Hudi 
迁移时激活) — 批 A T4
+- [x] `LogicalFileScan.computeOutput` 不再 `instanceof IcebergExternalTable / 
HMSExternalTable` —— **部分达成**:新增 `PluginDrivenExternalTable` 分支前置;Iceberg 分支保留作 
P6 fallback —— 批 A T5
+- 🟡 `PhysicalPlanTranslator` 不再 `import` 任何具体 `*ExternalTable` 类(除迁移期 
fallback) — **迁移期保留**(用户决议 Q3);7 个连接器特定分支在 P3-P7 各自迁移完成时随主任务删除
+- [x] fe-core 全编译 + checkstyle 0
+- [ ] PR CI 全绿(待批 A push + PR 创建后由 CI 报告)
+
+---
+
+## 任务清单
+
+> ID 永不复用。批次方案 2026-05-25 用户已确认:批 A=T3+T4+T5、批 B=T1、T2 推迟 P4/P5。
+
+| ID | 任务 | 批次 | Owner | 状态 | PR | 启动 | 完成 | 备注 |
+|---|---|---|---|---|---|---|---|---|
+| P1-T01 | 删除 13 个 `Jdbc*Client.java` + `JdbcFieldSchema.java` | **🚫 推迟到 P8** 
| — | 🚫 | — | — | — | 2026-05-25 recon 结论:3 个 fe-core 
caller(PostgresResourceValidator / StreamingJobUtils / 
CdcStreamTableValuedFunction)均为活的 CDC streaming 代码(非 dead code),删除需在 
ConnectorPlugin/ConnectorMetadata 上为 CDC 暴露 
`getPrimaryKeys`/`getColumnsFromJdbc`/`listTables` 等 capability。用户决议(Q4):不在 P1 
阶段做 SPI 扩展,T1 推迟到 P8 收尾,届时与 streaming CDC 重构一起做 |
+| P1-T02 | 重复 `PaimonPredicateConverter` + `McStructureHelper` 处理 | **🚫 推迟到 
P4/P5** | — | 🚫 | — | — | — | 2026-05-25 用户决议(Q2):fe-core caller 本身是 P4/P5 要删的 
legacy;本阶段不动 |
+| P1-T03 | `PhysicalPlanTranslator.visitPhysicalFileScan` 收口(**保留 fallback**) 
| **批 A** | @me | ✅ | TBD | 2026-05-25 | 2026-05-25 | 
`PluginDrivenExternalTable` 分支提到 if-else 链最前;7 个老分支原地保留作 P3-P7 迁移期 fallback |
+| P1-T04 | `visitPhysicalHudiScan` 委托给 `PluginDrivenScanNode` | **批 A** | @me 
| ✅ | TBD | 2026-05-25 | 2026-05-25 | 新分支前置;`scanParams` + `tableSnapshot` 经 
`FileQueryScanNode` setters 透传;`incrementalRelation` 待 P3 Hudi 迁移时 SPI 扩展(TODO 
注释已落) |
+| P1-T05 | `LogicalFileScan.computeOutput` 改走 SPI | **批 A** | @me | ✅ | TBD | 
2026-05-25 | 2026-05-25 | 新增 `computePluginDrivenOutput()`(与 
`computeIcebergOutput` 同 shape,用 `getFullSchema` + 
virtualColumns);`supportPruneNestedColumn` 加 `PluginDrivenExternalTable → 
false` 显式分支(无新 SPI capability 时保守默认);`IcebergExternalTable` 路径原地保留 |
+
+**状态图例**:⏳ pending / 🚧 in_progress / ✅ done / ❌ blocked / 🚫 deleted
+
+---
+
+## 阶段日志(倒序)
+
+### 2026-05-25(白天 ④)— P1 收尾:T1 推迟到 P8
+
+批 B (T1) 启动前 recon 结论:13 个 legacy JDBC client + JdbcFieldSchema 的 3 个 fe-core 
caller **均为活的 CDC streaming 代码**:
+
+- `PostgresResourceValidator.java`(`job/extensions/insert/streaming/`):CREATE 
JOB 时校验 PG 复制槽 / 发布,被 `StreamingJobUtils.validateSource` → 
`StreamingInsertJob.validateTvfSource` → `CreateJobCommand`/`AlterJobCommand` 
链路使用
+- `StreamingJobUtils.java`(`job/util/`):`getJdbcClient()` + 
`jdbcClient.getPrimaryKeys()` / `getColumnsFromJdbc()` / 
`getTablesNameList()`,CDC 表枚举 + DDL 生成
+- `CdcStreamTableValuedFunction.java`(`tablefunction/`):`cdc_stream` TVF,被 
`CdcStream.java:46` 调,streaming 作业执行链路
+
+测试侧:`StreamingJobUtilsTest` 
需重写;`JdbcFieldSchemaTest`/`JdbcClickHouseClientTest`/`JdbcClientExceptionTest` 
测 legacy 本身(随源删除)。fe-connector 侧 SPI 替换 `Jdbc*ConnectorClient` 已就位,但 **fe-core 
不能直接 import**(会破坏 `tools/check-connector-imports.sh` 守门)。
+
+**用户决议(Q4,2026-05-25)**:推迟 T1 到 P8 收尾。理由:
+- 删 T1 需要在 ConnectorPlugin/ConnectorMetadata 上为 CDC use case 暴露新 
capability(getPrimaryKeys / getColumnsFromJdbc / listTables),是 SPI 扩展工作,超出 
Master Plan §3.2 P1 scope
+- 现状无 runtime 风险——legacy JDBC client 仍在原位,CDC 功能正常
+- P8 收尾阶段与 streaming CDC 重构一起做,避免 P1 阶段引入 1-2 天计划外 SPI 设计工作
+
+**P1 in-scope 完成度**:T3+T4+T5 ✅;T1 推迟 P8;T2 推迟 P4/P5。P1 阶段关闭,准备 batch A push + 
PR,进入 P2 (trino-connector)。
+
+### 2026-05-25(白天 ③)— 批 A 编码完成(T3 + T4 + T5)
+
+实施了三处 SPI 收口(保留迁移期 fallback):
+
+- **T3** — `PhysicalPlanTranslator.visitPhysicalFileScan`:把现有 `if (table 
instanceof PluginDrivenExternalTable)` 分支提到 if-else 链最前;7 
个连接器特定分支(HMS/Iceberg/Paimon/Trino/MaxCompute/LakeSoul/RemoteDoris)原地保留作 P3-P7 
迁移期 fallback。
+- **T4** — `PhysicalPlanTranslator.visitPhysicalHudiScan`:在 method 顶部新增 
`PluginDrivenExternalTable` 分支,路由到 `PluginDrivenScanNode.create(...)`,通过 
`FileQueryScanNode` setters 透传 `tableSnapshot` / 
`scanParams`。`hudiScan.getIncrementalRelation()` 增量场景被记为 P3 Hudi SPI 扩展的 
TODO(注释已落)。HMS + DLAType.HUDI 路径保留。本分支今日不可达(PhysicalHudiScan 目前只为 
HMSExternalTable 创建),P3 Hudi 迁移时激活。
+- **T5** — `LogicalFileScan`:
+  - `computeOutput()`:新增 `PluginDrivenExternalTable` 分支,调新增 helper 
`computePluginDrivenOutput()`,用 `getFullSchema() + virtualColumns`(与 
`computeIcebergOutput` 同 shape)。JDBC/ES 当前无 hidden cols 也无 
virtualColumns,行为等价。Iceberg 分支原地保留。
+  - `supportPruneNestedColumn()`:新增 `PluginDrivenExternalTable → return false` 
显式分支。语义无变化(fall-through 也是 false),但显式声明 SPI 默认;未来加 `ConnectorCapability` 时改这里。
+  - 新增 import:`org.apache.doris.datasource.PluginDrivenExternalTable`。
+
+**编译 / Checkstyle**:`mvn -pl fe-core -am compile` BUILD SUCCESS;`mvn -pl 
fe-core checkstyle:check` 0 violations。
+
+**测试范围**:三处变更对 JDBC/ES(当前唯一已迁 SPI 连接器)行为等价(fullSchema == baseSchema 且无 
virtualColumns;supportPruneNestedColumn 原本就 false)。集成层信号依赖 PR CI 上的 JDBC + ES 
regression-test(P0 已基线 PASS)。本地单测层未新增——三处都是路由 reorder + 显式声明,难以在不引入 
PluginDrivenExternalTable mock 的前提下意义单测;待 PR review 决定是否补。
+
+### 2026-05-25(白天 ②)— 批次方案确认
+
+用户回复 3 个决策点(HANDOFF Q1/Q2/Q3):
+
+- **Q1 → A → B → C**:先做 T3+T4+T5 scan-node 收口(批 A),再删 legacy JDBC client(批 
B),T2 推迟到 P4/P5
+- **Q2 → 推迟 T2**:fe-core PaimonPredicateConverter + McStructureHelper 留到 P4/P5 
caller 删除时一并干掉;P1 不动
+- **Q3 → 保留 fallback**:T3 仅把 `PluginDrivenExternalTable` 分支提到最前;老 instanceof 
链原地保留,每个连接器在 P3-P7 迁移完成时删对应分支
+
+任务表的"批次"列已同步更新;T2 状态翻 🚫(推迟标记)。
+
+### 2026-05-25(白天)— 阶段启动 + recon
+
+- 新建分支 `catalog-spi-02` 基于 `upstream-apache/branch-catalog-spi`(PR #63582 已合入 
`c6f056fa5bd`)
+- Recon 5 个子任务,输出代码侧 facts:
+  - **T1**:13 个 `Jdbc*Client.java`(合计 ~2730 LOC)+ `JdbcFieldSchema.java`(129 
LOC)。fe-core 内 3 个外部 caller 
必须先解耦:`PostgresResourceValidator.java`、`StreamingJobUtils.java`、`CdcStreamTableValuedFunction.java`。3
 个测试需删或迁
+  - **T2**:fe-core 有 
`datasource/paimon/source/PaimonPredicateConverter.java`(201 LOC)和 
`datasource/maxcompute/McStructureHelper.java`(298 LOC)。fe-connector 侧的对应类是 
canonical 版本。fe-core 
caller:`PaimonScanNode`、`MaxComputeExternalCatalog`、`MaxComputeMetadataOps` 
自身就是 legacy,P4/P5 会删
+  - **T3**:`PhysicalPlanTranslator.visitPhysicalFileScan` lines 726-797(72 
LOC),含 8 个 instanceof 分支(HMSExternalTable + 嵌套 DLAType 路由;Iceberg / Paimon / 
Trino / MaxCompute / LakeSoul / RemoteDoris / 
PluginDrivenExternalTable)。`PluginDrivenScanNode.create(...)` 和 
`PluginDrivenExternalTable` 已存在
+  - **T4**:`visitPhysicalHudiScan` lines 821-841(21 LOC),目前断言 HMSExternalTable 
+ DLAType.HUDI,构造 HudiScanNode 时传 `getScanParams()` + 
`getIncrementalRelation()` 支持增量
+  - **T5**:`LogicalFileScan.computeOutput` lines 201-212(12 LOC),instanceof 
IcebergExternalTable 时走 `computeIcebergOutput()` 加 v3 row-lineage 
虚拟列。`supportPruneNestedColumn()` 也用了 3 个 instanceof(lines 236-238)
+  - **Bonus**:`nereids/` 目录下还有 ~62 处 `instanceof.*ExternalTable`;P1 范围只覆盖 
PhysicalPlanTranslator + LogicalFileScan,其余 50+ 处在 P3-P7 各连接器迁移时随主任务清理
+- 批次方案待用户确认(见 HANDOFF)
+
+---
+
+## 关联
+
+- Master plan 章节:[§3.2 P1 阶段](../00-connector-migration-master-plan.md)
+- RFC 章节:n/a(P1 是 SPI 消费方收口,不涉及 SPI 设计修改)
+- 决策:—
+- 偏差:—
+- 风险:R-008(文档脱节)、R-001(image 兼容回归——T3/T4/T5 收口须不影响序列化路径)
+- 连接器:jdbc(T1)、paimon(T2)、maxcompute(T2);T3-T5 是平台层
+
+---
+
+## 当前阻塞项
+
+无。P1 阶段关闭,剩余动作仅为 batch A push + PR 创建(待用户授权)。下一阶段 P2 (trino-connector) 可启动。


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to