
# Filtre

Filtrele controlează ce date sunt incluse într-un instantaneu. Excludeți tabelele de cache, ignorați coloanele cu marcaje temporale volatile sau omiteți rândurile tranzitorii. Filtrele se aplică în timpul capturării instantaneului și afectează toate operațiunile ulterioare (diff, merge, apply).

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

Controlează ce tabele sunt incluse într-un instantaneu. Acceptă modele glob prin PHP `fnmatch()`.

### Excluderea tabelelor

```php
use Merql\Filter\TableFilter;

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

Orice tabel care corespunde unui model de excludere este omis din instantaneu. Caracterul wildcard `*` corespunde oricărei secvențe de caractere.

### Includerea tabelelor

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

Sunt capturate numai tabelele care corespund unui model de includere. Toate celelalte sunt excluse.

### Cum funcționează

Metoda `apply()` a filtrului primește o listă de nume de tabele și returnează subsetul filtrat:

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

Când sunt prezente atât modele de includere, cât și de excludere (prin instanțe de filtre separate), excluderea este evaluată prima. Pentru a fi păstrat, un tabel nu trebuie să corespundă niciunui model de excludere și trebuie să corespundă cel puțin unui model de includere.

## ColumnFilter

Elimină anumite coloane din datele rândurilor în timpul capturării. Este util pentru coloanele volatile care se modifică la fiecare scriere și ar cauza diferențe false.

```php
use Merql\Filter\ColumnFilter;

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

### Cazuri de utilizare frecvente

- Marcaje temporale `updated_at` / `modified_at` care se modifică la fiecare salvare.
- Contoare `revision` care se incrementează automat.
- `session_token` sau alte câmpuri tranzitorii.

### Cum funcționează

Filtrul elimină coloanele numite din fiecare rând după calcularea cheii rândului:

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

Cheia rândului (identitatea) este calculată din rândul nefiltrat. Asta înseamnă că filtrul de coloane nu afectează niciodată coloanele de identitate. Sunt filtrate doar datele folosite pentru calcularea amprentei și compararea diferențelor.

## RowFilter

Filtrează rânduri individuale folosind o funcție predicate de tip closure. Closure-ul primește numele tabelului și datele complete ale rândului și returnează `true` pentru a include rândul sau `false` pentru a-l exclude.

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

### Cum funcționează

Metoda `shouldInclude()` este apelată pentru fiecare rând în timpul capturării:

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

Rândurile excluse nu primesc amprentă, nu sunt stocate în instantaneu și nu apar în diferențe sau îmbinări.

## Combinarea filtrelor

Transmiteți mai multe filtre către `capture()`. Se folosește cel mult un filtru din fiecare tip:

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

Ordinea procesării:

1. **TableFilter** rulează prima și stabilește ce tabele să fie capturate.
2. Pentru fiecare tabel, rândurile sunt citite din baza de date.
3. **RowFilter** rulează pentru fiecare rând și exclude rândurile care nu corespund.
4. **Cheia rândului** este calculată din rândul nefiltrat (cu coloanele de identitate intacte).
5. **ColumnFilter** elimină coloanele ignorate din datele rândului.
6. **Amprenta** este calculată din datele filtrate ale rândului.

Această ordine asigură că coloanele de identitate sunt întotdeauna disponibile pentru calcularea cheii, chiar dacă filtrul de coloane le elimină ulterior.

## Filtre și consecvență

Folosiți aceleași filtre pentru toate cele trei instantanee dintr-o îmbinare (base, ours, theirs). Dacă se folosesc filtre diferite, poate părea că au fost adăugate sau eliminate rânduri ori coloane, deși acestea au fost doar filtrate în mod diferit.

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