
# 筛选器

筛选器用于控制快照中包含哪些数据。你可以排除缓存表、忽略易变的时间戳列，或跳过临时行。筛选器会在捕获快照时应用，并影响所有后续操作（差异比较、合并和应用）。

```php
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

$snapshot = $snapshotter->capture('baseline', filters: [
    TableFilter::exclude(['sessions', 'cache_*']),
    ColumnFilter::ignore(['updated_at', 'modified_date']),
]);
```

## TableFilter

控制快照中包含哪些表。支持通过 PHP 的 `fnmatch()` 使用 glob 模式。

### 排除表

```php
use Merql\Filter\TableFilter;

$filter = TableFilter::exclude(['sessions', 'cache_*', 'wp_*_log']);
```

任何与排除模式匹配的表都会从快照中省略。`*` 通配符可匹配任意字符序列。

### 包含表

```php
$filter = TableFilter::include(['posts', 'comments', 'users']);
```

只捕获与包含模式匹配的表。其余表都会被排除。

### 工作原理

筛选器的 `apply()` 方法接收表名列表，并返回筛选后的子集：

```php
$allTables = ['posts', 'comments', 'sessions', 'cache_pages', 'users'];

$filter = TableFilter::exclude(['sessions', 'cache_*']);
$filtered = $filter->apply($allTables);
// ['posts', 'comments', 'users']

$filter = TableFilter::include(['posts', 'users']);
$filtered = $filter->apply($allTables);
// ['posts', 'users']
```

如果同时存在包含和排除模式（通过单独的筛选器实例提供），则先评估排除模式。表必须不匹配任何排除模式，并且至少匹配一个包含模式，才会被保留。

## ColumnFilter

在捕获期间从行数据中移除指定列。适用于每次写入都会变化、可能导致错误差异的易变列。

```php
use Merql\Filter\ColumnFilter;

$filter = ColumnFilter::ignore(['updated_at', 'modified_date', 'last_login']);
```

### 常见用例

- `updated_at` / `modified_at` 时间戳，每次保存都会变化。
- `revision` 自动递增计数器。
- `session_token` 或其他临时字段。

### 工作原理

行键计算完成后，筛选器会从每一行中移除指定列：

```php
$filter = ColumnFilter::ignore(['updated_at']);

$row = ['id' => 1, 'title' => 'Hello', 'updated_at' => '2026-01-15 10:30:00'];
$filtered = $filter->applyToRow($row);
// ['id' => 1, 'title' => 'Hello']
```

行键（标识）根据未筛选的行计算。这意味着列筛选器不会影响标识列。只有用于指纹计算和差异比较的数据会经过筛选。

## RowFilter

使用谓词闭包筛选单行。闭包接收表名和完整行数据，并返回 `true` 以包含该行，或返回 `false` 以排除该行。

```php
use Merql\Filter\RowFilter;

// Exclude draft posts.
$filter = RowFilter::create(function (string $table, array $row): bool {
    if ($table === 'posts' && ($row['status'] ?? '') === 'draft') {
        return false;
    }
    return true;
});

// Exclude rows with NULL email.
$filter = RowFilter::create(function (string $table, array $row): bool {
    if ($table === 'users') {
        return $row['email'] !== null;
    }
    return true;
});
```

### 工作原理

捕获期间会为每一行调用 `shouldInclude()` 方法：

```php
$include = $filter->shouldInclude('posts', ['id' => 1, 'status' => 'draft']);
// false
```

被排除的行不会计算指纹，不会存储在快照中，也不会出现在差异或合并结果中。

## 组合筛选器

向 `capture()` 传入多个筛选器。每种筛选器类型最多使用一个：

```php
$snapshot = $snapshotter->capture('baseline', filters: [
    TableFilter::exclude(['cache_*']),
    ColumnFilter::ignore(['updated_at']),
    RowFilter::create(fn($table, $row) => ($row['active'] ?? '1') === '1'),
]);
```

处理顺序：

1. 首先运行 **TableFilter**，确定要捕获哪些表。
2. 对每张表，从数据库读取行。
3. 对每一行运行 **RowFilter**，排除不匹配的行。
4. 根据未筛选的行计算 **行键**（身份列保持完整）。
5. **ColumnFilter** 从行数据中移除忽略的列。
6. 根据筛选后的行数据计算 **指纹**。

此顺序确保始终可以使用标识列计算键，即使这些列之后会被列筛选器移除。

## 筛选器与一致性

合并时，三个快照（base、ours、theirs）应使用相同的筛选器。如果使用不同的筛选器，某些行或列可能会看起来像是新增或删除，实际上只是筛选方式不同。

```php
// Use the same filter set for all snapshots.
$filters = [
    TableFilter::exclude(['sessions']),
    ColumnFilter::ignore(['updated_at']),
];

$base   = $snapshotter->capture('base', filters: $filters);
$ours   = $snapshotter->capture('ours', filters: $filters);
$theirs = $snapshotter->capture('theirs', filters: $filters);
```
