
# Filtre

Filtre styrer, hvilke data der medtages i et øjebliksbillede. Udeluk cachetabeller, ignorér flygtige tidsstempelkolonner, eller spring midlertidige rækker over. Filtre anvendes, når øjebliksbilledet tages, og påvirker alle efterfølgende handlinger (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

Styrer, hvilke tabeller der medtages i et øjebliksbillede. Understøtter glob-mønstre via PHPs `fnmatch()`.

### Udeluk tabeller

```php
use Merql\Filter\TableFilter;

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

Enhver tabel, der matcher et udelukkelsesmønster, udelades fra øjebliksbilledet. Jokertegnet `*` matcher enhver sekvens af tegn.

### Medtag tabeller

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

Kun tabeller, der matcher et medtagelsesmønster, indgår i optagelsen. Alle andre udelukkes.

### Sådan fungerer det

Filterets metode `apply()` tager en liste over tabelnavne og returnerer det filtrerede udvalg:

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

Når både medtagelses- og udelukkelsesmønstre er angivet (via separate filterinstanser), evalueres udelukkelse først. En tabel skal hverken matche et udelukkelsesmønster eller undlade at matche mindst ét medtagelsesmønster for at blive medtaget.

## ColumnFilter

Fjerner bestemte kolonner fra rækkedata under optagelsen. Nyttigt til flygtige kolonner, der ændres ved hver skrivning og ville medføre falske diff-resultater.

```php
use Merql\Filter\ColumnFilter;

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

### Almindelige anvendelser

- `updated_at` / `modified_at`-tidsstempler, der ændres ved hver lagring.
- `revision`-tællere, der automatisk tæller op.
- `session_token` eller andre midlertidige felter.

### Sådan fungerer det

Filteret fjerner navngivne kolonner fra hver række, efter at rækkenøglen er beregnet:

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

Rækkenøglen (identiteten) beregnes ud fra den ufiltrerede række. Det betyder, at identitetskolonner aldrig påvirkes af kolonnefilteret. Kun de data, der bruges til fingeraftryk og diff, filtreres.

## RowFilter

Filtrerer enkelte rækker ved hjælp af et prædikat-closure. Closuren modtager tabelnavnet og de fulde rækkedata og returnerer `true` for at medtage rækken eller `false` for at udelukke den.

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

### Sådan fungerer det

Metoden `shouldInclude()` kaldes for hver række under optagelsen:

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

Udelukkede rækker får ikke beregnet fingeraftryk, gemmes ikke i øjebliksbilledet og vises ikke i diff-resultater eller merges.

## Kombination af filtre

Send flere filtre til `capture()`. Der bruges højst ét filter af hver type:

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

Behandlingsrækkefølge:

1. **TableFilter** køres først og afgør, hvilke tabeller der skal medtages.
2. For hver tabel læses rækkerne fra databasen.
3. **RowFilter** køres på hver række og udelukker rækker, der ikke matcher.
4. **Rækkenøglen** beregnes ud fra den ufiltrerede række (identitetskolonnerne er intakte).
5. **ColumnFilter** fjerner ignorerede kolonner fra rækkedataene.
6. **Fingeraftrykket** beregnes ud fra de filtrerede rækkedata.

Denne rækkefølge sikrer, at identitetskolonner altid er tilgængelige, når nøglen beregnes, også selvom kolonnerne senere fjernes af kolonnefilteret.

## Filtre og konsistens

De samme filtre bør bruges til alle tre øjebliksbilleder i en merge (base, vores, deres). Hvis der bruges forskellige filtre, kan rækker eller kolonner se ud til at være tilføjet eller fjernet, selvom de blot er blevet filtreret forskelligt.

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