---
phase: 07-apiergonomics
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/parser.rs
  - src/lib.rs
  - tests/sqllog_additional.rs
  - tests/parser_coverage.rs
  - tests/integration_test.rs
  - tests/edge_cases.rs
  - tests/parser_iterator.rs
  - tests/parser_errors.rs
  - tests/parser_parallel.rs
  - tests/performance_metrics.rs
  - benches/parser_benchmark.rs
autonomous: true
requirements:
  - API-01
tags:
  - rust
  - builder
must_haves:
  truths:
    - "用户可以通过 LogParserBuilder::new(path).threads(N).parallel_threshold(N).build() 配置和创建 LogParser"
    - "LogParserBuilder::new(path) 接受 impl AsRef<Path>，与现有 from_path 一致"
    - "LogParser::from_path 已移除，所有现有代码和测试都使用 LogParserBuilder"
    - "FileEncodingHint 不再是 pub(crate)，用户可以导入并传给 builder 的 encoding_hint() 方法"
    - "build() 返回 Result<LogParser, ParseError>"
  artifacts:
    - path: "src/parser.rs"
      provides: "LogParserBuilder 结构体 + impl + LogParser constructor"
      contains: "pub struct LogParserBuilder"
    - path: "src/lib.rs"
      provides: "LogParserBuilder 和 FileEncodingHint 公开导出"
      contains: "LogParserBuilder"
  key_links:
    - from: "LogParserBuilder::build"
      to: "LogParser"
      via: "mmap + encoding detection"
      pattern: "fn build"
    - from: "LogParserBuilder::new"
      to: "impl AsRef<Path>"
      via: "method signature"
      pattern: "impl AsRef<Path>"
    - from: "lib.rs pub use"
      to: "LogParserBuilder"
      via: "re-export"
      pattern: "LogParserBuilder"
---

<objective>
新增 `LogParserBuilder` 链式配置 API，完全替代 `LogParser::from_path`。Builder 支持 `threads()`、`parallel_threshold()`、`encoding_hint()` 配置项。`build()` 执行 mmap + 编码探测并返回 `LogParser`。

Purpose: API-01 要求。决策 D-01（完全替代 from_path）、D-02（AsRef<Path> 签名）、D-03（配置项）具体实现。
Output: `LogParserBuilder` 结构体 + 实现 + 移除 from_path + 更新全部 11 个调用文件。
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/STATE.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/phases/07-apiergonomics/07-CONTEXT.md
@CLAUDE.md
@src/parser.rs
@src/error.rs
@src/sqllog.rs
@src/lib.rs
@tests/sqllog_additional.rs
@tests/parser_coverage.rs
@tests/integration_test.rs
@tests/edge_cases.rs
@tests/parser_iterator.rs
@tests/parser_errors.rs
@tests/parser_parallel.rs
@tests/performance_metrics.rs
@benches/parser_benchmark.rs
<interfaces>
Key types and signatures the executor needs:

src/parser.rs — LogParser struct (fields: mmap: Mmap, encoding: FileEncodingHint, 均为 private). LogParser::from_path 定义在 impl LogParser 块。par_iter() 内部 const PAR_THRESHOLD: usize = 32 * 1024 * 1024。FileEncodingHint 为 pub(crate) enum。

src/lib.rs — 当前 pub use: `pub use parser::{LogIterator, LogParser, RecordIndex, parse_record};` 和 `pub use sqllog::{MetaParts, PerformanceMetrics, Sqllog};` 和 `pub use error::ParseError;`

tests/ 中所有测试均用 `LogParser::from_path(file.path()).unwrap()` 模式。
benches/parser_benchmark.rs 中 8 处使用 `LogParser::from_path`。

src/lib.rs doc examples: 第 20 行 `let parser = LogParser::from_path("sqllog.txt")?;` 和第 40 行 `let parser = dm_database_parser_sqllog::LogParser::from_path("sqllog.txt")?;`
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create LogParserBuilder struct and impl</name>
  <files>src/parser.rs, src/lib.rs</files>
  <read_first>
    - src/parser.rs (LogParser struct 定义 lines 33-36, impl LogParser from_path 方法 lines 58-91, par_iter 内 PAR_THRESHOLD 常量 line 142, FileEncodingHint 定义 line 25-31)
    - src/lib.rs (pub use 列表 line 73-75)
    - .planning/phases/07-apiergonomics/07-CONTEXT.md (D-01, D-02, D-03 关于 Builder 签名和行为)
  </read_first>
  <action>
    在 `src/parser.rs` 中新增 `LogParserBuilder` 结构体和实现。同时在 `src/lib.rs` 中更新导出。

    **A. 新增 LogParserBuilder 结构体（在 LogParser struct 定义之后，recording Index 结构体之前）：**
    ```rust
    /// 配置并构建 LogParser 的构建器模式 API。
    ///
    /// # Example
    /// ```rust,no_run
    /// # use dm_database_parser_sqllog::LogParserBuilder;
    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// let parser = LogParserBuilder::new("sqllog.txt")
    ///     .threads(4)
    ///     .parallel_threshold(64 * 1024 * 1024)
    ///     .build()?;
    /// # Ok(())
    /// # }
    /// ```
    pub struct LogParserBuilder {
        path: PathBuf,
        threads: Option<usize>,
        parallel_threshold: Option<usize>,
        encoding_hint: Option<FileEncodingHint>,
    }
    ```
    需要新增 use 导入 `use std::path::PathBuf;`（检查文件顶部是否已有）。

    **B. 新增 LogParserBuilder impl 块：**
    ```rust
    impl LogParserBuilder {
        pub fn new<P: AsRef<Path>>(path: P) -> Self {
            Self {
                path: path.as_ref().to_path_buf(),
                threads: None,
                parallel_threshold: None,
                encoding_hint: None,
            }
        }

        pub fn threads(mut self, n: usize) -> Self {
            self.threads = Some(n);
            self
        }

        pub fn parallel_threshold(mut self, threshold: usize) -> Self {
            self.parallel_threshold = Some(threshold);
            self
        }

        pub fn encoding_hint(mut self, hint: FileEncodingHint) -> Self {
            self.encoding_hint = Some(hint);
            self
        }

        pub fn build(self) -> Result<LogParser, ParseError> {
            // 如果并行线程数已配置，尝试设置 rayon 全局线程池
            // build_global 只能调用一次；如已配置则静默忽略。
            if let Some(n) = self.threads {
                let _ = rayon::ThreadPoolBuilder::new()
                    .num_threads(n)
                    .build_global();
            }

            let file = File::open(&self.path)
                .map_err(|e| ParseError::IoError(e.to_string()))?;
            let mmap = unsafe {
                Mmap::map(&file).map_err(|e| ParseError::IoError(e.to_string()))?
            };

            #[cfg(unix)]
            let _ = mmap.advise(Advice::Sequential);

            // 根据 encoding_hint 确定编码
            // Some(hint) 时跳过自动探测直接使用指定编码
            // None 时执行与 from_path 相同的 head+tail 采样
            let encoding = match self.encoding_hint {
                Some(hint) => hint,
                None => {
                    let head_size = mmap.len().min(64 * 1024);
                    let tail_start = mmap.len().saturating_sub(4 * 1024).max(head_size);
                    let head_ok = simd_from_utf8(&mmap[..head_size]).is_ok();
                    let tail_ok = tail_start >= mmap.len()
                        || simd_from_utf8(&mmap[tail_start..]).is_ok();
                    if head_ok && tail_ok {
                        FileEncodingHint::Utf8
                    } else {
                        FileEncodingHint::Gb18030
                    }
                }
            };

            let parallel_threshold = self.parallel_threshold.unwrap_or(32 * 1024 * 1024);

            Ok(LogParser {
                mmap,
                encoding,
                parallel_threshold,
            })
        }
    }
    ```

    **C. 修改 LogParser 结构体：**
    新增 `parallel_threshold: usize` 字段：
    ```rust
    pub struct LogParser {
        mmap: Mmap,
        encoding: FileEncodingHint,
        parallel_threshold: usize,
    }
    ```
    field 顺序：mmap, encoding, parallel_threshold。

    **D. 修改 LogParser::par_iter() 方法：**
    将行 142 的 `const PAR_THRESHOLD: usize = 32 * 1024 * 1024;` 替换为使用 `self.parallel_threshold`：
    ```rust
    // 替换：const PAR_THRESHOLD: usize = 32 * 1024 * 1024;
    // 使用 self.parallel_threshold（builder 配置或默认值 32MB）
    ```

    **E. 修改 FileEncodingHint 可见性：**
    将 `pub(crate) enum FileEncodingHint` 改为 `pub enum FileEncodingHint`。添加 pub doc comment。
    确保 sync_lints（clippy）不产生 missing_docs 警告；如果 clippy 要求，给每个 variant 加简单 doc comment。

    **F. 更新 src/lib.rs 导出：**
    在 parser 的 pub use 行中添加 `LogParserBuilder` 和 `FileEncodingHint`：
    ```rust
    pub use parser::{LogIterator, LogParser, LogParserBuilder, RecordIndex, FileEncodingHint, parse_record};
    ```

    不允许的改动：
    - 不要修改 Sqllog、ParseError 或其他模块
    - 不要更改 par_iter 的分区逻辑，只替换常量引用
    - 不要删除 iter() 或 index() 方法
    - 不要修改 LogIterator 结构体
  </action>
  <verify>
    <automated>cargo build 2>&1 | grep -c "^error" | xargs test 0 -eq</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c "pub struct LogParserBuilder" src/parser.rs` 输出为 1
    - `grep -c "pub fn build" src/parser.rs` 输出为 1
    - `grep "parallel_threshold" src/parser.rs | grep "self\\.parallel_threshold"` 至少一行（par_iter 引用了 self.parallel_threshold）
    - `grep -c "pub enum FileEncodingHint" src/parser.rs` 输出为 1（不再是 pub(crate)）
    - `grep "LogParserBuilder" src/lib.rs` 至少一行（lib.rs 导出）
    - `grep "FileEncodingHint" src/lib.rs` 至少一行（lib.rs 导出）
    - `grep "parallel_threshold: usize" src/parser.rs | head -1` 输出非空（LogParser 新增字段）
    - `cargo build` 退出码 0
    - `cargo clippy --all-targets -- -D warnings` 退出码 0
  </acceptance_criteria>
  <done>
    LogParserBuilder 结构体完整，支持 new/threads/parallel_threshold/encoding_hint/build 链式调用。LogParser 新增 parallel_threshold 字段，par_iter 使用它。FileEncodingHint 公开。lib.rs 导出 Builder。编译通过。
  </done>
</task>

<task type="auto">
  <name>Task 2: Remove LogParser::from_path, update all callers</name>
  <files>src/parser.rs, src/lib.rs, tests/sqllog_additional.rs, tests/parser_coverage.rs, tests/integration_test.rs, tests/edge_cases.rs, tests/parser_iterator.rs, tests/parser_errors.rs, tests/parser_parallel.rs, tests/performance_metrics.rs, benches/parser_benchmark.rs</files>
  <read_first>
    - src/parser.rs (from_path 方法定义 lines 58-91, 需要完整读取确认替换为 LogParserBuilder::new(x).build().unwrap() 模式)
    - src/lib.rs (doc examples lines 20 和 40)
    - 所有 tests/*.rs 文件（每个文件的 imports 和 from_path 调用位置)
    - benches/parser_benchmark.rs（8 处 from_path 调用)
    - .planning/phases/07-apiergonomics/07-CONTEXT.md (D-01: 完全替代 from_path)
  </read_first>
  <action>
    移除 `LogParser::from_path` 方法，更新全部 11 个文件中的调用点和导入。

    **A. 移除 src/parser.rs 中的 from_path：**
    删除 from_path 方法定义（lines 58-91）。在 impl LogParser 块中保留 iter()、index()、par_iter()。如果 from_path 是 impl LogParser 块的最后一个方法或唯一方法，则 impl 块本身也需要保留（不能为空）。

    **B. 更新 src/lib.rs 的 doc examples：**
    第 1 个 doc example（line 20）：
    ```
    改前：let parser = LogParser::from_path("sqllog.txt")?;
    改后：let parser = LogParserBuilder::new("sqllog.txt").build()?;
    ```
    需要在文件顶部添加 import `LogParserBuilder`：
    ```
    改前：use dm_database_parser_sqllog::LogParser;
    改后：use dm_database_parser_sqllog::{LogParser, LogParserBuilder};
    ```
    第 2 个 doc example（line 40）：
    ```
    改前：let parser = dm_database_parser_sqllog::LogParser::from_path("sqllog.txt")?;
    改后：let parser = dm_database_parser_sqllog::LogParserBuilder::new("sqllog.txt").build()?;
    ```

    **C. 更新所有测试文件（10 个文件）：**
    每个测试文件中，将每处 `LogParser::from_path(file.path()).unwrap()` 替换为 `LogParserBuilder::new(file.path()).build().unwrap()`，`LogParser::from_path(tmp.path()).unwrap()` 替换为 `LogParserBuilder::new(tmp.path()).build().unwrap()`。

    同时处理测试文件中 import LogParser 的行，如果文件只有 LogParser 导入则改为导入 LogParserBuilder；如果已有多个导入则添加 LogParserBuilder。

    具体文件清单：
    1. tests/sqllog_additional.rs — 3 处 from_path，在 line 153/234/258
    2. tests/parser_coverage.rs  — 2 处 from_path，在 line 40/60
    3. tests/integration_test.rs — 2 处 from_path，在 line 13/39
    4. tests/edge_cases.rs       — 1 处 from_path，在 line 43
    5. tests/parser_iterator.rs  — 2 处 from_path，在 line 14/35
    6. tests/parser_errors.rs    — 2 处 from_path，在 line 13/29
    7. tests/parser_parallel.rs  — 8 处 from_path
    8. tests/performance_metrics.rs — 检查是否使用 from_path（该文件使用 parse_record，可能不需要改）
    9. benches/parser_benchmark.rs — 8 处 from_path

    对于 `LogParser::from_path(tm_path).unwrap()`（benchmark 中的 tm_path/tmp_path 等变量名），同样替换为 `LogParserBuilder::new(path_var).build().unwrap()`。

    对于 `.expect("open")` 或 `.expect("...")` 的 from_path 调用：
    ```
    改前：LogParser::from_path(tmp.path()).expect("open")
    改后：LogParserBuilder::new(tmp.path()).build().expect("open")
    ```

    **D. 更新 src/lib.rs 的 pub use 列表：**
    检查是否需要调整。当前 `pub use parser::{LogIterator, LogParser, RecordIndex, parse_record};` 中 LogParser 还在使用（iter, par_iter, index 仍在其上），保持不变。仅新增 LogParserBuilder 导出。

    代码一致性要求：
    - 不要修改任何测试逻辑（仅替换构造方式）
    - 不要在替换中引入额外的 unwrap() 或其他错误处理
    - 保持原 error handling 模式的 .expect() 或 .unwrap() 不变
  </action>
  <verify>
    <automated>cargo build 2>&1 | grep -c "^error" | xargs test 0 -eq</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c "fn from_path" src/parser.rs` 输出为 0（from_path 已移除）
    - `grep -rn "LogParser::from_path" src/ tests/ benches/` 输出为空（全部替换）
    - `grep -rn "LogParserBuilder" tests/` 至少 10 行（多个测试文件导入并使用）
    - `cargo build` 退出码 0
    - `cargo test` 退出码 0（全部测试通过）
    - `cargo clippy --all-targets -- -D warnings` 退出码 0
    - `cargo llvm-cov --workspace --all-features --fail-under-lines 90` 退出码 0
  </acceptance_criteria>
  <done>
    LogParser::from_path 完全移除。全部 10 个测试文件和 benchmark 文件使用 LogParserBuilder。所有测试通过，覆盖率达到 90%。
  </done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| user-provided path -> Builder::build() | 用户传入的文件路径经 File::open + mmap 打开；恶意路径（如 /dev/null）可能导致 mmap 异常 |
| Builder::threads() -> rayon global pool | 用户设置线程数可能影响全局 rayon 线程池 |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-07-01 | Denial of Service | LogParserBuilder::build() | accept | mmap 超大文件可能导致 OOM，但这与现有 from_path 行为一致，非本 phase 引入的新风险 |
| T-07-02 | Tampering | Builder::threads() rayon pool | accept | build_global 只能调用一次，多次调用静默忽略。用户文档注明此行为。不影响正确性。 |
| T-07-SC | Tampering | 依赖注入 | mitigate | 不新增依赖。`rayon` 已是现有依赖。 |
</threat_model>

<verification>
- `cargo build` 编译通过
- `cargo test` 全部通过
- `cargo clippy --all-targets -- -D warnings` 无新增警告
- `cargo llvm-cov --workspace --all-features --fail-under-lines 90` 覆盖率 >= 90%
</verification>

<success_criteria>
- LogParserBuilder 可链式构造：`LogParserBuilder::new(path).threads(4).build()`
- build() 正确处理 encoding_hint（指定编码 + 自动探测两种路径）
- build() 返回 Result<LogParser, ParseError>（IO 错误传递）
- parallel_threshold 默认 32 MB，可通过 Builder 覆盖
- FileEncodingHint 公开可见
- LogParser::from_path 不存在于任何文件中
- 全部 8 个测试文件和 1 个 benchmark 文件使用 LogParserBuilder
</success_criteria>

<output>
After completion, create `.planning/phases/07-apiergonomics/07-01-SUMMARY.md` documenting:
- LogParserBuilder struct 字段和每个方法的签名
- LogParser 新增 parallel_threshold 字段
- FileEncodingHint 已公开导出
- 已更新调用 from_path 的文件数量
</output>
