---
phase: 07-apiergonomics
plan: 02
type: execute
wave: 2
depends_on:
  - 07-01
files_modified:
  - src/parser.rs
  - tests/parser_filters.rs
autonomous: true
requirements:
  - API-02
tags:
  - rust
  - filters
must_haves:
  truths:
    - "用户可以调用 parser.iter().filter_by_exec_time(100) 过滤出执行时间超过 100ms 的记录"
    - "用户可以使用 filter_by_sql_contains('SELECT') 过滤出 SQL 体包含特定关键字的记录"
    - "解析错误的记录被过滤方法自动丢弃（不返回 Err）"
    - "没有 EXECTIME 字段的记录被 filter_by_exec_time 自动过滤掉"
    - "API-05（通用过滤适配器）未实现（deferred）"
  artifacts:
    - path: "src/parser.rs"
      provides: "LogIterator::filter_by_exec_time + filter_by_sql_contains"
      contains: "fn filter_by_exec_time" in impl LogIterator block
    - path: "tests/parser_filters.rs"
      provides: "过滤方法的测试用例"
      contains: "test_filter_by_exec_time"
  key_links:
    - from: "LogIterator::filter_by_exec_time"
      to: "Sqllog::parse_performance_metrics"
      via: "内部解析 exectime 并与 min_ms 比较"
      pattern: "parse_performance_metrics"
    - from: "LogIterator::filter_by_sql_contains"
      to: "Sqllog::body"
      via: "contains 匹配"
      pattern: "\\.body()\\.contains"
---

<objective>
在 `LogIterator` 上添加两个专用过滤方法：`filter_by_exec_time(min_ms)` 和 `filter_by_sql_contains(pattern)`，返回 `impl Iterator<Item = Result<Sqllog<'a>, ParseError>>`。不引入新类型或新 trait。

Purpose: API-02 要求。决策 D-04（LogIterator impl 上添加）、D-05（至少两个方法）、D-06（不引入新类型）的具体实现。
Output: 两个过滤方法 + 对应测试。
</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/sqllog.rs
@src/lib.rs
@tests/performance_metrics.rs

<interfaces>
Key types and signatures the executor needs:

```rust
// src/parser.rs — LogIterator (由 Plan 1 保留不变)
pub struct LogIterator<'a> {
    data: &'a [u8],
    pos: usize,
    encoding: FileEncodingHint,
    line_number: u64,  // 来自 Phase 6
}
// Iterator impl in same file — LogIterator 的 impl<'a> LogIterator 块
// filter_by_exec_time / filter_by_sql_contains 放到此 impl 块内

// src/sqllog.rs — Sqllog body() and parse_performance_metrics()
impl<'a> Sqllog<'a> {
    pub fn body(&self) -> Cow<'a, str> { ... }    // 已有
    pub fn parse_performance_metrics(&self) -> PerformanceMetrics<'a> { ... }  // 已有
}

// PerformanceMetrics 字段
pub struct PerformanceMetrics<'a> {
    pub exectime: f32,       // 执行时间（毫秒），default 0.0
    pub rowcount: u32,       // 行数，default 0
    pub exec_id: i64,        // 执行 ID
    pub sql: Cow<'a, str>,   // SQL 语句
}

// 构造方式（Plan 1 之后）：
// let parser = LogParserBuilder::new("path").build().unwrap();
// parser.iter()          // LogIterator
// parser.iter().filter_by_exec_time(100)  // filtered iterator
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add filter_by_exec_time method to LogIterator + tests</name>
  <files>src/parser.rs, tests/parser_filters.rs</files>
  <read_first>
    - src/parser.rs (impl LogIterator 块位置—LogIterator struct 定义之后立即有 impl<'a> LogIterator 块，需在文件内定位。Iterator impl for LogIterator 位于不同块，不要混淆。)
    - tests/performance_metrics.rs (build_record 函数的模式，了解如何构造含 EXECTIME 的测试记录)
    - tests/parser_errors.rs (测试文件的导入风格、#[cfg(not(miri))] 门控使用方式)
    - .planning/phases/07-apiergonomics/07-CONTEXT.md (D-04: 方法签名; D-05: filter_by_exec_time(min_ms: u64); D-06: 不引入新类型)
  </read_first>
  <action>
    在 `src/parser.rs` 的 `impl<'a> LogIterator<'a>` 块中添加 `filter_by_exec_time` 方法。在 `tests/parser_filters.rs` 中编写对应测试。

    **A. 方法定义（添加到 impl LogIterator 块）：**

    方法签名和实现：
    ```rust
    /// 过滤出执行时间大于等于 `min_ms` 毫秒的记录。
    ///
    /// 使用 `parse_performance_metrics()` 内部解析逻辑，不重复实现。
    /// 不包含 EXECTIME 字段的记录和解析错误的记录将被过滤掉。
    ///
    /// # Example
    /// ```rust,no_run
    /// # use dm_database_parser_sqllog::LogParserBuilder;
    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// let parser = LogParserBuilder::new("sqllog.txt").build()?;
    /// for record in parser.iter().filter_by_exec_time(100) {
    ///     // record: Result<Sqllog, ParseError>
    ///     println!("{}", record?.body());
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub fn filter_by_exec_time(
        self,
        min_ms: u64,
    ) -> impl Iterator<Item = Result<Sqllog<'a>, ParseError>> + 'a {
        self.filter(move |item| match item {
            Ok(sqllog) => {
                let metrics = sqllog.parse_performance_metrics();
                metrics.exectime >= min_ms as f32
            }
            Err(_) => false,
        })
    }
    ```
    在方法上添加 doc comment（包括 # Example doc test）。

    注意：`parse_performance_metrics` 返回的 `PerformanceMetrics` 的 `exectime` 默认值为 `0.0`。当记录不含 EXECTIME 字段时，`exectime == 0.0`，`>= min_ms` 在 `min_ms > 0` 时为 `false`，自动过滤掉。当 `min_ms == 0` 时，所有记录（含无 EXECTIME 的记录）都被保留，这与语义一致（"0ms 以上"包括所有记录）。

    **B. 测试文件 tests/parser_filters.rs：**
    创建新的测试文件，遵循现有测试模式（使用 `LogParserBuilder`、`NamedTempFile`、`#[cfg(not(miri))]`、`cargo test`）：

    ```rust
    use dm_database_parser_sqllog::LogParserBuilder;
    use std::io::Write;
    use tempfile::NamedTempFile;
    ```

    测试 1（#[cfg(not(miri))]）: `test_filter_by_exec_time_filters_low_exec_time`
    - 构造一个文件：第一条 EXECTIME 5ms（应被过滤），第二条 EXECTIME 200ms（应保留）
    - 在两条记录之前可能需要一条不含 EXECTIME 的记录
    - 断言结果数为 1（仅保留 >=100ms 的记录）
    - 断言保留的记录 SQL body 包含标识文本

    测试 2（#[cfg(not(miri))]）: `test_filter_by_exec_time_keeps_high_exec_time`
    - 两条记录都在阈值之上（如 150ms 和 300ms，threshold=100）
    - 断言结果数为 2

    测试 3（#[cfg(not(miri))]）: `test_filter_by_exec_time_empty_when_all_below`
    - 两条记录都低于阈值
    - 断言结果为空

    每条测试记录格式示例（带 EXECTIME 的有效记录）：
    ```
    2025-11-17 16:09:41.123 (EP[0] sess:1 thrd:2 user:u trxid:3 stmt:4 appname:a) SELECT 1 EXECTIME: 5(ms) ROWCOUNT: 1(rows) EXEC_ID: 1.
    ```
    注意记录必须以 `\n` 结尾。

    **C. 检查文件已有 impl LogIterator 块的位置：**
    用 grep 确认 `impl<'a> LogIterator<'a>` 的位置。该方法应添加到该块中（不是 `impl Iterator for LogIterator` 块）。
  </action>
  <verify>
    <automated>cargo test --test parser_filters 2>&1 | tail -10</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c "fn filter_by_exec_time" src/parser.rs` 输出为 1
    - `grep -c "parse_performance_metrics" src/parser.rs` 在新方法上下文中出现
    - `grep -c "test_filter_by_exec_time_filters_low_exec_time" tests/parser_filters.rs` 输出为 1
    - `grep -c "test_filter_by_exec_time_keeps_high_exec_time" tests/parser_filters.rs` 输出为 1
    - `grep -c "test_filter_by_exec_time_empty_when_all_below" tests/parser_filters.rs` 输出为 1
    - `cargo build` 退出码 0
    - `cargo test --test parser_filters` 3 个测试全部通过
    - `cargo test` 全 workspace 不破坏其他测试
  </acceptance_criteria>
  <done>
    filter_by_exec_time 方法实现并在 LogIterator impl 块中。3 个测试覆盖正常过滤、全部保留、全部过滤三种情况。全部测试通过。
  </done>
</task>

<task type="auto">
  <name>Task 2: Add filter_by_sql_contains method to LogIterator + tests</name>
  <files>src/parser.rs, tests/parser_filters.rs</files>
  <read_first>
    - src/parser.rs (确认 filter_by_exec_time 添加位置，在其后添加 filter_by_sql_contains)
    - .planning/phases/07-apiergonomics/07-CONTEXT.md (D-04: 方法签名; D-05: filter_by_sql_contains(pattern: &str))
  </read_first>
  <action>
    在 `src/parser.rs` 的 `impl<'a> LogIterator<'a>` 块中添加 `filter_by_sql_contains` 方法。在 `tests/parser_filters.rs` 末尾追加对应测试。

    **A. 方法定义（在 filter_by_exec_time 之后，同一 impl 块内）：**

    ```rust
    /// 过滤出 SQL 语句体包含指定 `pattern` 的记录。
    ///
    /// 使用 `body()` 方法获取 SQL 体并进行字符串包含检查。
    /// 解析错误的记录将被过滤掉。pattern 匹配区分大小写。
    ///
    /// 注意：`pattern` 参数会通过 `to_string()` 转换为 owned String 以避开生命周期约束，
    /// 因此该分配只在方法调用时发生一次，不计入每条记录的开销。
    ///
    /// # Example
    /// ```rust,no_run
    /// # use dm_database_parser_sqllog::LogParserBuilder;
    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// let parser = LogParserBuilder::new("sqllog.txt").build()?;
    /// for record in parser.iter().filter_by_sql_contains("SELECT") {
    ///     println!("{}", record?.body());
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub fn filter_by_sql_contains(
        self,
        pattern: &str,
    ) -> impl Iterator<Item = Result<Sqllog<'a>, ParseError>> + 'a {
        let pattern = pattern.to_string();
        self.filter(move |item| match item {
            Ok(sqllog) => sqllog.body().contains(&pattern),
            Err(_) => false,
        })
    }
    ```

    **B. 追加测试到 tests/parser_filters.rs（在第一个测试块之后）：**

    测试 4（#[cfg(not(miri))]）: `test_filter_by_sql_contains_matches`
    - 文件含 3 条记录：一条含 "SELECT"（应保留），一条含 "INSERT"（应过滤），一条含 "select"（区分大小写，应过滤）
    - threshold pattern = "SELECT"
    - 断言结果为 1 条

    测试 5（#[cfg(not(miri))]）: `test_filter_by_sql_contains_empty_when_no_match`
    - 文件含 2 条记录，都不含 pattern
    - 断言结果为 0

    测试 6（#[cfg(not(miri))]）: `test_filter_by_sql_contains_skips_parse_errors`
    - 文件含：一条无效记录（不含时间戳）、一条含 pattern 的有效记录
    - 断言结果为 1 条，SQL body 包含 pattern

    记录格式与 Task 1 一致（有效记录包含时间戳、meta、SQL body，以 \n 为间隔）。

    不需要在按行测试中精确构造 EXECTIME 等指标字段。
  </action>
  <verify>
    <automated>cargo test --test parser_filters 2>&1 | tail -10</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c "fn filter_by_sql_contains" src/parser.rs` 输出为 1
    - `grep "\\.body()\\.contains" src/parser.rs` 至少 1 行
    - `grep -c "filter_by_sql_contains" tests/parser_filters.rs` 至少 3 行（方法名出现 3 次 = 3 个测试）
    - `cargo build` 退出码 0
    - `cargo test --test parser_filters` 6 个测试全部通过（Task 1 的 3 个 + Task 2 的 3 个）
    - `cargo test` 全 workspace 不破坏其他测试
    - `cargo clippy --all-targets -- -D warnings` 退出码 0
  </acceptance_criteria>
  <done>
    filter_by_sql_contains 方法实现。6 个测试覆盖文本匹配、不匹配、跳过错误三种情况。全部测试通过。
  </done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| user-provided filter pattern -> filter_by_sql_contains | 用户提供的 pattern 字符串与每条记录的 SQL body 做 contains 比较；pattern 长度无上限 |
| filter closed over state | filter_by_exec_time 闭包捕获 min_ms（u64，Copy）；filter_by_sql_contains 闭包捕获 pattern（String，clone） |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-07-03 | Denial of Service | filter_by_sql_contains | accept | pattern 为任意长字符串 + 特大 SQL body 时 contains 耗时可能较高。但 pattern 为用户输入且 body() 已在 2x 256 字节指标窗口范围中，风险有限。 |
| T-07-04 | Denial of Service | filter_by_exec_time | accept | parse_performance_metrics 已是热路径，每次调用都有 O(indicators_window) 开销。过滤方法过滤掉大部分记录时减少后续处理量，反而有益。 |
| T-07-SC | Tampering | 依赖注入 | mitigate | 不新增依赖。 |
</threat_model>

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

<success_criteria>
- `parser.iter().filter_by_exec_time(100)` 返回 `impl Iterator<Item = Result<Sqllog<'a>, ParseError>>`
- 不含 EXECTIME 的记录被过滤掉
- `parser.iter().filter_by_sql_contains("SELECT")` 按 SQL body 包含 pattern 过滤
- pattern 区分大小写
- 解析错误的记录被自动丢弃
- 不引入 FilteredIter 新类型或 SqllogFilter 新 trait
</success_criteria>

<output>
After completion, create `.planning/phases/07-apiergonomics/07-02-SUMMARY.md` documenting:
- filter_by_exec_time 签名和内部逻辑
- filter_by_sql_contains 签名和内部逻辑（pattern.to_string() 的原因）
- 测试覆盖情况
</output>
