
# フィルター

フィルターは、スナップショットに含めるデータを制御します。キャッシュテーブルを除外したり、変化しやすいタイムスタンプ列を無視したり、一時的な行をスキップしたりできます。フィルターはスナップショット取得時に適用され、後続のすべての操作（差分比較、マージ、適用）に影響します。

```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']);
```

includeパターンに一致するテーブルのみが取得され、それ以外はすべて除外されます。

### 仕組み

フィルターの`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']
```

別々のフィルターインスタンスでincludeパターンとexcludeパターンの両方を指定した場合、excludeが先に評価されます。保持するには、テーブルがどのexcludeパターンにも一致せず、少なくとも1つのincludeパターンに一致する必要があります。

## 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()`に複数のフィルターを渡せます。各フィルタータイプは最大1つまで使用されます。

```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. フィルター後の行データから**フィンガープリント**を計算します。

この順序により、列フィルターで後から削除される場合でも、キーの計算に識別列を常に利用できます。

## フィルターと整合性

マージする3つのスナップショット（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);
```
