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
commit 2b1a3bb2197c07cc11616f797e3710a7bd7b41e3 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 9e921555422..179693fd7ae 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<Env> | | 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]
