Skip to content

Commit 709ce81

Browse files
committed
feat(view): add database tree source
Signed-off-by: Eli Ma <eli@patch.sh>
1 parent ab92129 commit 709ce81

8 files changed

Lines changed: 766 additions & 7 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mega2"
3-
version = "0.42.3"
3+
version = "0.42.4"
44
edition = "2024"
55
license = "MIT OR Apache-2.0"
66
description = "Monorepo engine and Git hosting server for AI agents: trunk-based storage, Git Smart HTTP, Git LFS, OCI registry, and Agent Session Capture"

‎docs/plan/plan-20261002.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@
161161
| 2026-10-06 | HP-09 实施与 ER-05 复审 | 新增八张视图数据表、八个 SeaORM 实体及注册迁移;六条目录级迁移用例覆盖精确 schema、无外键、重放幂等、既有 schema 不变、实体往返、故障回滚和恢复。格式、Clippy、全量测试与两个构建门通过。Claude HP09-R1 与 Codex HP09-R2 均 PASS;`v0.42.1` 的 Docker D 组按发布者指令不等待、不重试。 | 无拆卡 | HP-09、Review log、ER-04 C/D |
162162
| 2026-10-06 | HP-10 实施与 ER-05 复审 | 新增根链扩展、段事务与预算、SQLSTATE 锁超时处理、根链冲突核对、共享进程内告警去重、锁键和真实对象夹具;设计 §4.4 补 `hash32` 定义,§4.6 将 L_V(F) 的键改为 `(VIEW_FILTER_LOCK_NS, hash32(F.pk))`。六个定向用例覆盖首父链、预算/重启/双连接交错、不连续与冲突、等锁失败和锁键隔离。格式、Clippy、全量测试与两个构建门通过。Docker D 组按发布者指令不等待、不重试。 | 无拆卡 | HP-10、设计 §4.4/§4.6、Review log、ER-04 C/D |
163163
| 2026-10-06 | HP-28 实施与 ER-05 复审 | 新增可嵌入单快照 SQL 的 `ROOT_CHAIN_HALTED_SQL` 与无锁 `root_chain_halted`,将五类根链不连续的停追、跨连接池复现、常数语句数、无写入、正常中间态和清空暂存表后的恢复/重新判定写入七个 lib 用例。格式、Clippy、全量测试与两个构建门通过。Claude HP28-R2/R3 与 Codex HP28-R4 均 PASS;`v0.42.3` 的 Docker D 组按发布者指令不等待、不重试。 | 无拆卡 | HP-28、事实校准第 7 项、Review log、ER-04 C/D |
164+
| 2026-10-06 | HP-29 实施与 ER-05 复审 | 新增可失败的 1000 条分块提交/tree 读取及数据库 `ViewTreeSource`;六个 lib 用例覆盖泛型连接/事务、失败隔离、缓存语义、查找顺序、显式 hash kind 与语句计数。设计 §4.4 登记分块语句数。Claude HP29-R3 与 Codex HP29-R4 均 PASS;`v0.42.4` 的 Docker D 组按发布者指令不等待、不重试。 | 无拆卡 | HP-29、设计 §4.4、Review log、ER-04 C/D |
164165

165166
## 已决议设计决策
166167

@@ -2361,7 +2362,7 @@ schema 与连接池:每类不连续、每种状态各用一个 schema;第 2
23612362

23622363
**Task type:** `implementation`
23632364

2364-
**Lifecycle / Acceptance:** `pending` / 空
2365+
**Lifecycle / Acceptance:** `remote-pending` / `locally-accepted`(本地验收已完成;承载发布 `v0.42.4` 的 Docker D 组按发布者指令不等待、不重试)
23652366

23662367
**Description:** 自 HP-11 拆出。为视图路径提供不会 panic 的提交与 tree 读取,让「查询失败」「行不存在」「无法解析」「未预取」四种情形在返回值上可以区分(设计 §4.1「缺对象不等于路径不存在」、§0.5-21、§6.9 `batch_size` 行)。查询失败只出现在可失败批量读取与异步预取返回的 `Err(MegaError)` 中;后三种是 HP-04 的 `MissingObject { tree_id, reason }` 的三种原因。本卡遵守 HP-04 已定的 `TreeSource` 契约:读取方法对一个 tree id 返回 `Result<Tree, MissingObject>`,不做 I/O;数据库只在异步预取阶段访问。范围:
23672368
- `MonoStorage::get_commits_by_hashes_fallible` 与 `MonoStorage::get_trees_by_hashes_fallible`:
@@ -2437,7 +2438,7 @@ schema 与连接池:每类不连续、每种状态各用一个 schema;第 2
24372438
以下 cargo 用例需要 Postgres 测试栈(ER-01),前置(环境准备,不单独计门):`source .env.test && docker compose -p mega2-it -f docker/docker-compose.test.yml up -d --wait`。Postgres 缺失时用例 panic,判为失败,不是跳过。两个计数用例之外,连接一律用 `test_db_connection` 加 `apply_migrations(&db, true)`,守卫随连接存活(GC-HP-10)。夹具行由测试直接写库(ADR-HP-06)。用例运行在 `#[tokio::test]` 的单线程运行时上。`view_tree_source` 的用例一律经 HP-04 的 `read_tree(kind, source, id)` 读取,以本卡实例为 `source`,传入的 kind 与构造本实例的 kind 相同。用例说明中标为「推出」或「计数核对」的断言,由所注明的条目或 HP-04 的契约推出,不是另立的谓词。
24382439

24392440
- [ ] `source .env.test && cargo test -p mega2 --lib 'jupiter::storage::mono_storage::tests::fallible_batch_reads_rows_then_query_error' -- --exact`(new;判定第 1 条,以及第 3 条中的两个 `MonoStorage` 函数。先在未改动的测试 schema 中:两个函数的请求集分别混入库中不存在的 id、重复 id、跨越分块边界的 2500 个 id,返回 `Ok`,行与交集一一对应;每个函数在连接上与在 `begin()` 得到的 `DatabaseTransaction` 上各调用一次,结果相同。事务上的调用完成后先 `commit()` 或 `rollback()` 结束该事务,再改列名(未结束的事务持有表锁,ALTER 会无限等待)。再在本用例的测试 schema 内执行 `ALTER TABLE mega_commit RENAME COLUMN tree TO hp29_gone` 与 `ALTER TABLE mega_tree RENAME COLUMN sub_trees TO hp29_gone`,使两表的查询报错;只改列名、不改表名,也不用 `DatabaseConnection::default()`;请求 1 个与 2500 个 id 时,两个函数都返回 `Err(MegaError)`;用例不 panic)
2440-
- [ ] `source .env.test && cargo test -p mega2 --lib 'jupiter::storage::mono_storage::tests::fallible_batch_reads_chunk_statement_count' -- --exact`(new;判定第 2 条中的两个 `MonoStorage` 函数:用 `test_db_config` 取得 `(db_config, _schema)`,守卫持有到用例结束;经 `database_connection(&db_config)` 建连接(内含迁移),在该连接上设 `set_metric_callback` 计数,每次计数前清零;不在 `test_db_connection` 返回的连接上替换回调(GC-HP-10)。n 取 0、1、1000、1001、2500,语句数依次为 0、1、1、2、3;1001 个不同 id 再混入 500 个重复 id 时仍为 2 条)
2441+
- [ ] `source .env.test && cargo test -p mega2 --lib 'jupiter::storage::mono_storage::tests::fallible_batch_reads_chunk_statement_count' -- --exact`(new;判定第 2 条中的两个 `MonoStorage` 函数:用 `test_db_config` 取得 `(db_config, _schema)`,守卫持有到用例结束;经 `database_connection(&db_config)` 建连接(内含迁移),在该连接上设 `set_metric_callback` 计数,每次计数前清零;不在 `test_db_connection` 返回的连接上替换回调(GC-HP-10)。n 取 0、1、1000、1001、2500,语句数依次为 0、1、1、2、3;1000 个不同 id 再混入 1 个重复 id 时仍为 1 条(不去重将为 2 条))
24412442
- [ ] `source .env.test && cargo test -p mega2 --lib 'jupiter::storage::view_tree_source::tests::lookup_order_view_object_first' -- --exact`(new;判定第 5 条的查找顺序与解析方法:预取后经 `read_tree` 读取,只在 `mega_view_object`(kind = 2)中的脊柱 tree 与只在 `mega_tree` 中的 L0 tree 都能读到。该 L0 tree 行的 `sub_trees` 能按 kind 解析,但其 hash 不等于行的 `tree_id`:`read_tree` 返回 `Tree`,其 id 等于所请求的 `tree_id`,条目与该行字节的解析结果相同(读取不校验 hash,设计 §4.1「原样复用的 L0 tree」);同一 id 在 `mega_view_object` 中可解析、在 `mega_tree` 中是坏字节时,读到前者;`mega_view_object` 中同 id 的行 kind = 1 时,读到 `mega_tree` 中的 tree。sha1 与 sha256 各验一次,构造本实例与调用 `read_tree` 都显式传入该 kind;sha1 一例在 `set_hash_kind_for_test(HashKind::Sha256)` 的作用域内运行,sha256 一例在 `set_hash_kind_for_test(HashKind::Sha1)` 的作用域内运行,以此间接证明本卡代码不调用 `Tree::from_mega_model` 与 `get_hash_kind`(GC-HP-07);sha256 一例的实例在 `DatabaseTransaction` 上构造)
24422443
- [ ] `source .env.test && cargo test -p mega2 --lib 'jupiter::storage::view_tree_source::tests::read_tree_outcomes_over_db_source' -- --exact`(new;判定第 6、7、8 条。sha1 与 sha256 各一套夹具:sha1 夹具在 `set_hash_kind_for_test(HashKind::Sha256)` 的作用域内运行,sha256 夹具在 `set_hash_kind_for_test(HashKind::Sha1)` 的作用域内运行。预取后两表都没有的 id,得到原因为「行不存在」的 `MissingObject`。`mega_tree` 行的 `sub_trees`、`mega_view_object` 行的 `data` 各去掉最后一个字节(截在最后一个条目的 id 内部,不落在条目边界上),预取后得到原因为「无法解析」的 `MissingObject`。kind 为 sha256 时预取并读取 `mega_tree` 中一个 40 位 sha1 id 的行,kind 为 sha1 时预取并读取 `mega_tree` 中一个 64 位 sha256 id 的行,都得到原因为「无法解析」的 `MissingObject`。从未预取的 id 得到原因为「未预取」的 `MissingObject`,原因不是「行不存在」。各例的 `tree_id` 都等于请求的 id;用例不 panic。推出:为「行不存在」的 id 补上 `mega_tree` 行后,原实例再次预取并读取仍为「行不存在」,由第 2 条的待查 id 定义(已记入缺失集合的 id 不再查询)与第 6 条推出;新建实例预取后读到该 tree,由第 1、5 条推出)
24432444
- [ ] `source .env.test && cargo test -p mega2 --lib 'jupiter::storage::view_tree_source::tests::prefetch_statement_count' -- --exact`(new;判定第 2 条中 `prefetch` 对 `mega_view_object` 的读取:用 `test_db_config` 取得 `(db_config, _schema)`,守卫持有到用例结束;经 `database_connection(&db_config)` 建连接(内含迁移),各实例都构造在这条连接上;在该连接上设 `set_metric_callback` 计数,每次计数前清零;不在 `test_db_connection` 返回的连接上替换回调(GC-HP-10)。第一个实例预取 1500 个待查 id,其中 300 个只在 `mega_view_object`(kind = 2)、1000 个只在 `mega_tree`、200 个两表都没有,语句数为 ⌈1500/1000⌉ + ⌈1200/1000⌉ = 4。以下三个场景各新建一个实例:预取那 300 个 id 时为 1 条,不查 `mega_tree`;空请求为 0 条;只含 EMPTY_TREE 的请求为 0 条。第一个实例再次预取这 1500 个 id,语句数增量为 0。后四项按第 2 条的待查 id 定义计算。计数核对:第一个实例预取之后经 `read_tree` 逐个读取这 1500 个 id,语句数增量为 0,由 HP-04 的同步 trait 保证)
@@ -6014,6 +6015,10 @@ Result 只允许 `PASS` 或 `FAIL`。`FAIL` 必须列出 P0/P1 条目,并在
60146015
| HP28-R2 | HP-28 修订:同快照断言、正常态输入与文档,Claude Code 2.1.288 独立只读复审 | PASS | P0 0;P1 0;P2 0 | P3 1:覆盖文字可能被理解为所有正常态均读取计数;后续文字修订关闭 | Claude Code 2.1.288 只读输出 |
60156016
| HP28-R3 | HP-28 文档澄清复审,Claude Code 2.1.288 独立只读复审 | PASS | P0 0;P1 0;P2 0 | P3 1:内部状态标签脱离任务卡不够自明;后续文字修订关闭 | Claude Code 2.1.288 只读输出 |
60166017
| HP28-R4 | HP-28 最终差异、事实校准第 7 项、七项验证与门禁,Codex 独立复审 | PASS | P0 0;P1 0;P2 0 | SQL 的自包含性与有序判定、无锁单查询、跨连接池持久性、恢复/重新停追、同快照并发观察、文档锚点和所有门禁已逐项核对 | `libra diff --check`、七项 HP-28 Verification、格式、Clippy、构建门与全量测试 |
6018+
| HP29-R1 | HP-29 初始实现,Claude Code 独立只读复审 | FAIL | P0 0;P1 0;P2 3:去重边界、kind=1 回退区分和文档覆盖不足;均在后续修订关闭 | P3 3:单 id 错误、事务夹具与集合 A 覆盖;后续修订关闭 | Claude Code 只读输出;复审见 HP29-R2 |
6019+
| HP29-R2 | HP-29 修订,Claude Code 独立只读复审 | FAIL | P0 0;P1 0;P2 1:任务卡重复 id 样本与测试不一致;后续修订关闭 | P3:L0 条目比较与发布登记;前者在 R3 前关闭 | Claude Code 只读输出;复审见 HP29-R3 |
6020+
| HP29-R3 | HP-29 最终实现、任务卡与设计 §4.4,Claude Code 独立只读复审 | PASS | P0 0;P1 0;P2 0 | P3:连接路径行数断言和发布登记;行数断言在提交前补齐 | Claude Code 只读输出 |
6021+
| HP29-R4 | HP-29 最终差异、六项验证与文档,Codex 独立复审 | PASS | P0 0;P1 0;P2 0 | 泛型分块、局部预取提交、三种缺失原因、EMPTY_TREE、测试计数和设计/集成文档逐项核对 | 当前差异、六项 HP-29 Verification、`libra diff --check` |
60176022

60186023
## 非目标与延后项
60196024

‎docs/refactoring/history-projection.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,7 @@ fn mark_ready_if_covered(F, tip) -> bool: # 只在持
845845
return true
846846
```
847847
- **往返次数。** 每批 O(1) 条提交查询,再加 O(深度) 条按层的 tree 查询;不存在"每个提交一次往返"。
848+
- **可失败批量读取的语句数(HP-29)。** 一层有 k 个待查 tree id、其中 r 个不在 `mega_view_object` 中时,先查询 `mega_view_object`、再查询剩余 `mega_tree`,至多执行 ⌈k/1000⌉ + ⌈r/1000⌉ 条语句;每条最多 1000 个 id。伪代码的「每层 1 条 SQL」和 4.1 的「同一层只发一次查询」按此分块规则理解:一层 id 不超过 1000 时至多两条。每层语句数只取决于该层 id 数,与批内提交数无关,因此 O(深度) 不变。提交的 `get_commits_by_hashes_fallible` 也按 1000 分块:请求 m 个不同提交 id 时执行 ⌈m/1000⌉ 条;若按伪代码传入完整 rows,m 至多 B + 1,默认 B = 1000 且 s0 ≥ 1 的满批为 1001 个 id、两条查询,B ≤ 10 000 时至多 11 条。第 s0 行只提供父 tree 时可从 `mega_view_root_chain.tree_id` 取得它,不读取该提交则 m ≤ B;提交读取的「1 条 SQL」按此理解,仍为每批 O(1)。
848849
- **批的前提【决策】。** `s0 < tip` 时,本批必须恰好取到 seq 从 `max(s0,1)` 到 `min(s0+B, tip)` 的连续行,因此至少包含 `seq = s0+1` 这一行,水位每批至少前进 1。
849850
- 取不到这些行,说明派生状态或配置已经损坏,原因可能是根链表出现空洞,或者 B < 1 绕过了 6.9 的校验。
850851
- 此时本批不写任何行、不推进水位,记错误、告警并退出循环,不得原地重试。

‎docs/refactoring/integration.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,28 @@ Try/Blocking/取消等锁;以及全部锁键与同 schema 的两两隔离。
233233
连接池或重启的用例通过 `test_db_config` 保持同一 schema,并用
234234
`database_connection` 创建新的连接池。
235235

236+
## DB 模块覆盖:视图 tree 读取(HP-29)
237+
238+
`MonoStorage::get_commits_by_hashes_fallible` 与
239+
`MonoStorage::get_trees_by_hashes_fallible` 先对请求 id 去重,再以 1000 条为一组在给定
240+
连接或事务上读取;查询错误以 `MegaError` 返回,数据库中没有的 id 不会被误报为查询错误。
241+
`ViewTreeSource` 只在异步 `prefetch` 中访问数据库:它先读 `mega_view_object(kind = 2)`,
242+
再用剩余 id 读 `mega_tree`,并且只在两个阶段都成功后写入本实例的已预取与缺失集合。同步的
243+
`TreeSource::read_tree` 不发 SQL;已确认缺行、坏字节和未预取 id 分别给出 `Absent`、
244+
`Malformed` 与 `Unprefetched`。EMPTY_TREE 不进入预取集合,由 HP-04 的 `read_tree` 直接处理。
245+
246+
六个 lib 用例是 `fallible_batch_reads_rows_then_query_error`、
247+
`fallible_batch_reads_chunk_statement_count`、`lookup_order_view_object_first`、
248+
`read_tree_outcomes_over_db_source`、`prefetch_statement_count` 和
249+
`prefetch_query_error_keeps_state`:它们分别覆盖连接和事务上的去重、缺行、分块和查询错误;
250+
视图对象优先于 L0 tree、kind = 1 回退、显式 SHA-1/SHA-256 解析及事务构造;三种
251+
`MissingObject` 原因和补行后的新实例读取;`prefetch` 的 1000 条分块、缓存命中与同步读取
252+
零查询;以及在测试 schema 内改列名后,预取失败不污染既有缓存或把新 id 误记为缺失。
253+
语句计数用 `test_db_config` 自建连接并安装 metric callback,避免替换
254+
`test_db_connection` 持有 schema 守卫的回调。失败注入只改列名,不改表名,也不用
255+
`DatabaseConnection::default()`。每个批量查询的语句数为 ⌈n / 1000⌉;HP-11 的语句数断言
256+
按同一公式计算。调用方收到 `Unprefetched` 说明漏做预取,它不表示对象缺失。
257+
236258
## 迁移覆盖:`merge_queue.requester` nullable 列(UN-18)
237259

238260
**历史表。** [`plan-20260910.md`](../plan/plan-20260910.md) MW-05 已 `DROP TABLE merge_queue`;下列描述 UN-18 当时的加列迁移,不再是现行 schema。

‎src/jupiter/storage/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub mod view_root_chain;
2727
pub mod view_storage;
2828
#[cfg(test)]
2929
pub(crate) mod view_test_fixtures;
30+
pub mod view_tree_source;
3031
pub mod webhook_storage;
3132

3233
use std::sync::Arc;

0 commit comments

Comments
 (0)