
# Filtry

Filtry określają, które dane zostaną uwzględnione w migawce. Możesz wykluczyć tabele pamięci podręcznej, zignorować zmienne kolumny ze znacznikami czasu albo pominąć tymczasowe wiersze. Filtry są stosowane podczas tworzenia migawki i wpływają na wszystkie dalsze operacje (różnicowanie, scalanie, zastosowanie).

```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

Określa, które tabele zostaną uwzględnione w migawce. Obsługuje wzorce glob za pomocą PHP `fnmatch()`.

### Wykluczanie tabel

```php
use Merql\Filter\TableFilter;

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

Każda tabela pasująca do wzorca wykluczającego jest pomijana w migawce. Symbol wieloznaczny `*` pasuje do dowolnego ciągu znaków.

### Uwzględnianie tabel

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

Rejestrowane są tylko tabele pasujące do wzorca uwzględniającego. Wszystkie pozostałe są wykluczane.

### Jak to działa

Metoda `apply()` filtra przyjmuje listę nazw tabel i zwraca przefiltrowany podzbiór:

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

Gdy występują jednocześnie wzorce uwzględniające i wykluczające (w osobnych instancjach filtrów), najpierw sprawdzane są wzorce wykluczające. Tabela zostaje zachowana, jeśli nie pasuje do żadnego wzorca wykluczającego i pasuje do co najmniej jednego wzorca uwzględniającego.

## ColumnFilter

Usuwa określone kolumny z danych wierszy podczas tworzenia migawki. Przydatne w przypadku zmiennych kolumn, które zmieniają się przy każdym zapisie i powodowałyby fałszywe różnice.

```php
use Merql\Filter\ColumnFilter;

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

### Typowe zastosowania

- `updated_at` / `modified_at` znaczniki czasu, które zmieniają się przy każdym zapisie.
- `revision` liczniki automatycznie zwiększane.
- `session_token` lub inne pola tymczasowe.

### Jak to działa

Filtr usuwa nazwane kolumny z każdego wiersza po obliczeniu klucza wiersza:

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

Klucz wiersza (tożsamość) jest obliczany na podstawie nieprzefiltrowanego wiersza. Oznacza to, że filtr kolumn nigdy nie wpływa na kolumny tożsamości. Filtrowane są tylko dane używane do obliczania odcisku i różnic.

## RowFilter

Filtruje poszczególne wiersze za pomocą funkcji predykatu. Funkcja otrzymuje nazwę tabeli i pełne dane wiersza, a następnie zwraca `true`, aby uwzględnić wiersz, lub `false`, aby go wykluczyć.

```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;
});
```

### Jak to działa

Metoda `shouldInclude()` jest wywoływana dla każdego wiersza podczas tworzenia migawki:

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

Wykluczone wiersze nie są uwzględniane przy obliczaniu odcisku, nie są zapisywane w migawce ani nie pojawiają się w różnicach lub scalonych danych.

## Łączenie filtrów

Przekaż wiele filtrów do `capture()`. Używany jest najwyżej jeden filtr każdego typu:

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

Kolejność przetwarzania:

1. **TableFilter** działa jako pierwszy i określa, które tabele zarejestrować.
2. Dla każdej tabeli odczytywane są wiersze z bazy danych.
3. **RowFilter** działa na każdym wierszu i wyklucza te, które nie pasują.
4. **Klucz wiersza** jest obliczany na podstawie nieprzefiltrowanego wiersza (z zachowaniem kolumn tożsamości).
5. **ColumnFilter** usuwa ignorowane kolumny z danych wiersza.
6. **Odcisk** jest obliczany na podstawie przefiltrowanych danych wiersza.

Taka kolejność zapewnia dostępność kolumn tożsamości przy obliczaniu klucza, nawet jeśli filtr kolumn usuwa je na późniejszym etapie.

## Filtry a spójność

Wszystkie trzy migawki w scalaniu (base, ours, theirs) powinny korzystać z tych samych filtrów. Przy użyciu różnych filtrów wiersze lub kolumny mogą wyglądać na dodane albo usunięte, choć w rzeczywistości zostały tylko inaczej przefiltrowane.

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