
# Filtros

Los filtros controlan qué datos se incluyen en una instantánea. Excluya tablas de caché, ignore columnas de marcas de tiempo volátiles u omita filas transitorias. Los filtros se aplican durante la captura y afectan a todas las operaciones posteriores (comparación, combinación, aplicación).

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

Controla qué tablas se incluyen en una instantánea. Admite patrones glob mediante el `fnmatch()` de PHP.

### Excluir tablas

```php
use Merql\Filter\TableFilter;

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

Toda tabla que coincida con un patrón de exclusión se omite de la instantánea. El comodín `*` coincide con cualquier secuencia de caracteres.

### Incluir tablas

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

Solo se capturan las tablas que coinciden con un patrón de inclusión. Todas las demás se excluyen.

### Cómo funciona

El método `apply()` del filtro recibe una lista de nombres de tablas y devuelve el subconjunto filtrado:

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

Cuando hay patrones de inclusión y exclusión (en instancias de filtro distintas), la exclusión se evalúa primero. Para conservar una tabla, esta no debe coincidir con ningún patrón de exclusión y debe coincidir con al menos uno de inclusión.

## ColumnFilter

Quita columnas concretas de los datos de las filas durante la captura. Es útil para columnas volátiles que cambian con cada escritura y provocarían diferencias falsas.

```php
use Merql\Filter\ColumnFilter;

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

### Casos de uso habituales

- Marcas de tiempo `updated_at` / `modified_at` que cambian con cada guardado.
- Contadores `revision` que se incrementan automáticamente.
- `session_token` u otros campos transitorios.

### Cómo funciona

El filtro elimina las columnas indicadas de cada fila después de calcular su clave:

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

La clave de fila (identidad) se calcula a partir de la fila sin filtrar. Esto significa que el filtro de columnas nunca afecta a las columnas de identidad. Solo se filtran los datos usados para calcular huellas digitales y comparar.

## RowFilter

Filtra filas individuales mediante un cierre predicado. El cierre recibe el nombre de la tabla y todos los datos de la fila, y devuelve `true` para incluirla o `false` para excluirla.

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

### Cómo funciona

El método `shouldInclude()` se llama para cada fila durante la captura:

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

Las filas excluidas no se someten a cálculo de huellas digitales, no se guardan en la instantánea ni aparecen en comparaciones o combinaciones.

## Combinar filtros

Pase varios filtros a `capture()`. Se usa como máximo un filtro de cada tipo:

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

Orden de procesamiento:

1. **TableFilter** se ejecuta primero y determina qué tablas se capturan.
2. Se leen las filas de cada tabla de la base de datos.
3. **RowFilter** se aplica a cada fila y excluye las que no coinciden.
4. Se calcula la **clave de fila** a partir de la fila sin filtrar (con las columnas de identidad intactas).
5. **ColumnFilter** elimina las columnas ignoradas de los datos de la fila.
6. Se calcula la **huella digital** a partir de los datos filtrados de la fila.

Este orden garantiza que las columnas de identidad estén siempre disponibles para calcular la clave, aunque el filtro de columnas las elimine posteriormente.

## Filtros y coherencia

Se deben usar los mismos filtros para las tres instantáneas de una combinación (base, nuestra y suya). Si se usan filtros distintos, puede parecer que se han añadido o eliminado filas o columnas cuando, en realidad, solo se filtraron de manera diferente.

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