
# Referência de PHP API

Referência completa de todas as classes e métodos públicos em merql.

## Merql (fachada)

`Merql\Merql` é o ponto de entrada estático para operações comuns. Ele encapsula as classes de nível inferior e gerencia automaticamente a persistência dos snapshots.

```php
use Merql\Connection;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;
use Merql\Merql;

$connection = Connection::sqlite(':memory:');
Merql::init($connection, identityRules: new IdentityRuleSet([
    'options' => IdentityRule::natural(['option_name']),
]));
```

### `init(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null): void`

Inicializa a fachada com uma conexão de banco de dados. Deve ser chamado antes de `snapshot()`, `apply()` ou `applyGuarded()`; chame-o novamente para trocar de banco, por exemplo, para criar snapshots de ours e theirs a partir de duas cópias diferentes. Sem um `$driver`, ele chama `DriverFactory::create()`, que gera uma exceção para mecanismos diferentes de MySQL e SQLite, a menos que um driver esteja registrado. Passe um driver para substituir a detecção automática, ou um conjunto de regras de identidade para substituir a identidade de linha por tabela durante a captura do snapshot.

### `snapshot(string $name, array $tables = []): Snapshot`

Captura o estado atual do banco de dados e o salva em disco. Passe um array de nomes de tabelas para criar snapshots de tabelas específicas ou omita-o para incluir todas as tabelas.

```php
$snapshot = Merql::snapshot('baseline');
$snapshot = Merql::snapshot('baseline', ['posts', 'users']);
```

### `diff(string $base, string $current): Changeset`

Carrega dois snapshots nomeados e calcula o conjunto de alterações entre eles. Não requer `init()`, pois opera com snapshots persistidos.

```php
$changeset = Merql::diff('before', 'after');
```

### `merge(string $base, string $ours, string $theirs): MergeResult`

Mesclagem em três vias de três snapshots nomeados. Retorna um resultado com operações sem conflitos e eventuais conflitos. Mescla no nível de coluna: a fachada constrói `ThreeWayMerge` sem `CellMergeConfig`, portanto a mesclagem no nível da célula não é aplicada. Para isso, use diretamente [`ThreeWayMerge`](#threewaymerge).

```php
$result = Merql::merge('base', 'ours', 'theirs');
```

### `patch(string $base, string $changes): MergeResult`

Mesclagem em duas vias: aplica alterações sobre a base. Equivale a `merge($base, $base, $changes)`. Sempre gera um resultado sem conflitos, pois não há edições simultâneas. Somente no nível de coluna, como em `merge()`.

```php
$result = Merql::patch('base', 'changes');
```

### `apply(MergeResult $result): ApplyResult`

Executa um resultado de mesclagem sem conflitos no banco de dados passado para `init()`. Requer `init()`. Gera `ConflictException` se ainda houver conflitos não resolvidos.

Todas as operações do resultado são executadas; por isso, o resultado completo de `merge()` só se aplica corretamente a um banco de dados que ainda contenha o estado base. Para um banco de dados que já contenha um dos lados, aplique uma seleção das operações que lhe faltam; consulte [Aplicação protegida](https://merql.inline0.dev/pt/docs/apply/guarded).

```php
$applied = Merql::apply($result);
echo $applied->rowsAffected();
```

### `plan(string $id, string $base, string $ours, string $theirs, array $metadata = []): MergePlan`

Cria um plano de mesclagem pronto para UI a partir de três snapshots nomeados. O plano inclui IDs estáveis de operação, IDs de grupos de alterações, contexto da operação, conflitos, incompatibilidades de esquema, um resumo, metadados e um hash do plano. A mesclagem ocorre no nível de coluna, como em `merge()`; passe um resultado mesclado por célula para `MergePlanBuilder::build()` para fazer a mesclagem nesse nível.

```php
$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs', [
    'source' => 'review-ui',
]);
```

### `applyGuarded(MergeResult $result, Snapshot $expectedLive): ApplyResult`

Aplica um resultado de mesclagem sem conflitos com pré-condições otimistas nas linhas ativas. As atualizações e exclusões exigem que as linhas ativas atuais correspondam a `$expectedLive`; as inserções exigem que a identidade de destino ainda não exista. `$expectedLive` é um snapshot do banco de dados de destino (theirs, ao promover ours para o banco de dados de onde theirs foi obtido). Qualquer instrução que afete zero linhas reverte a transação.

```php
use Merql\Snapshot\SnapshotStore;

$applied = Merql::applyGuarded($selectedResult, SnapshotStore::load('theirs'));
```

### `reset(): void`

Limpa as instâncias internas de conexão e Snapshotter. Usado principalmente em testes para redefinir o estado entre execuções.

---

## Conexão

`Merql\Connection` cria adaptadores de conexão com padrões sensatos. As conexões PDO usam exceções em caso de erro, modo de busca associativo, sem prepared statements emulados e valores buscados convertidos em strings. O adaptador mysqli normaliza os valores buscados para o mesmo formato de string ou null.

### `mysql(string $host, string $database, string $username, string $password = '', int $port = 3306, string $charset = 'utf8mb4'): PdoDatabaseConnection`

```php
use Merql\Connection;

$connection = Connection::mysql('127.0.0.1', 'my_app', 'root', 'secret');
```

### `mysqli(string $host, string $database, string $username, string $password = '', int $port = 3306, string $charset = 'utf8mb4'): MysqliDatabaseConnection`

```php
$connection = Connection::mysqli('127.0.0.1', 'my_app', 'root', 'secret');
```

### `sqlite(string $path = ':memory:'): PdoDatabaseConnection`

Cria uma conexão SQLite. Ativa automaticamente as chaves estrangeiras por meio de `PRAGMA foreign_keys = ON`.

```php
$connection = Connection::sqlite('/var/data/app.sqlite');
$connection = Connection::sqlite(':memory:');
```

### `fromDsn(string $dsn, string $username = '', string $password = ''): PdoDatabaseConnection`

Conecte-se usando qualquer string DSN do PDO. merql inclui drivers apenas para `mysql` e `sqlite`; outros DSNs abrem uma conexão, mas `Merql::init()`, `Snapshotter` e os aplicadores lançam `Unsupported database driver`, a menos que um driver seja registrado com `DriverFactory::register()`.

```php
$connection = Connection::fromDsn('mysql:host=localhost;dbname=app', 'root', 'pass');
```

### `fromPdo(PDO $pdo): PdoDatabaseConnection`

Envolve uma instância PDO existente.

### `fromMysqli(mysqli $mysqli, string $charset = 'utf8mb4'): MysqliDatabaseConnection`

Envolve uma instância mysqli existente.

### Contrato do adaptador

Todos os adaptadores implementam `Merql\Database\DatabaseConnection`:

```php
interface DatabaseConnection
{
    public function driverName(): string;
    public function execute(string $sql, array $params = []): int;
    public function query(string $sql, array $params = []): array;
    public function scalar(string $sql, array $params = []): ?string;
    public function beginTransaction(): void;
    public function commit(): void;
    public function rollBack(): void;
    public function lastInsertId(): string;
}
```

`execute()`, `query()` e `scalar()` aceitam parâmetros posicionais `list<scalar|null>` para placeholders `?`. `query()` retorna `list<array<string, scalar|null>>`; os valores são normalizados para o mesmo formato de string ou null nos adaptadores PDO e mysqli.

---

## Capturador de snapshots

`Merql\Snapshot\Snapshotter` captura o estado do banco de dados. Ao contrário da fachada, oferece controle total sobre os filtros e não persiste os dados automaticamente.

### `__construct(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null)`

O driver é detectado automaticamente pela identidade da conexão, caso não seja informado. Passe um `IdentityRuleSet` quando a identidade da linha específica da tabela precisar substituir os padrões do esquema.

### `capture(string $name, array $tables = [], array $filters = []): Snapshot`

Capture o estado atual. Passe nomes de tabelas para limitar o escopo. Passe objetos de filtro para excluir tabelas, colunas ou linhas.

```php
use Merql\Snapshot\Snapshotter;
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Filter\RowFilter;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;

$snapshotter = new Snapshotter(
    $connection,
    identityRules: new IdentityRuleSet([
        'wp_options' => IdentityRule::natural(['option_name']),
    ]),
);

// All tables.
$snapshot = $snapshotter->capture('full');

// Specific tables with filters.
$snapshot = $snapshotter->capture('filtered', ['posts', 'users'], [
    TableFilter::exclude(['cache_*', 'sessions']),
    ColumnFilter::ignore(['updated_at', 'created_at']),
    RowFilter::create(fn(string $table, array $row) => $row['status'] !== 'trash'),
]);
```

### `captureAliased(string $name, array $physicalToCanonicalTableMap, array $filters = []): Snapshot`

Capture tabelas físicas usando nomes canônicos de tabela. Use isso quando a mesma tabela lógica tiver nomes físicos diferentes entre ambientes.

```php
$snapshot = $snapshotter->captureAliased('sandbox', [
    'wp_onumia_abcd_posts' => 'wp_posts',
    'wp_onumia_abcd_postmeta' => 'wp_postmeta',
]);
```

O snapshot resultante armazena nomes canônicos de tabela e preserva o esquema físico da tabela, as colunas de identidade configuradas, as chaves de linha e os filtros.

### `fromData(string $name, array $tableData): Snapshot` (estático)

Crie um snapshot a partir de dados brutos, sem conexão com o banco de dados. Útil para testes ou para importar dados de outra fonte.

```php
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\TableSnapshotData;
use Merql\Schema\TableSchema;

$schema = new TableSchema('posts', ['id' => 'int', 'title' => 'varchar'], ['id']);

$snapshot = Snapshotter::fromData('test', [
    'posts' => new TableSnapshotData($schema, [
        ['id' => '1', 'title' => 'Hello'],
        ['id' => '2', 'title' => 'World'],
    ], ['id']),
]);
```

---

## Snapshot

`Merql\Snapshot\Snapshot` é um objeto de valor somente leitura que representa um estado completo do banco de dados.

```php
$snapshot->name;                    // string: snapshot identifier
$snapshot->tables;                  // array<string, TableSnapshot>
$snapshot->tableNames();            // list<string>
$snapshot->hasTable('posts');       // bool
$snapshot->getTable('posts');       // ?TableSnapshot
```

---

## TableSnapshot

`Merql\Snapshot\TableSnapshot` contém as linhas, as impressões digitais e o esquema de uma tabela.

```php
$table = $snapshot->getTable('posts');

$table->schema;                    // TableSchema
$table->identityColumns;           // list<string>: columns used for row identity
$table->fingerprints;              // array<string, string>: row key to hash
$table->rows;                      // array<string, array<string, mixed>>

$table->rowKeys();                 // list<string>
$table->rowCount();                // int
$table->hasRow('42');              // bool
$table->getRow('42');              // ?array<string, mixed>
$table->getFingerprint('42');      // ?string
```

---

## TableSnapshotData

`Merql\Snapshot\TableSnapshotData` são dados de entrada para `Snapshotter::fromData()`.

```php
use Merql\Snapshot\TableSnapshotData;
use Merql\Schema\TableSchema;

$data = new TableSnapshotData(
    schema: new TableSchema('posts', ['id' => 'int', 'title' => 'varchar'], ['id']),
    rows: [
        ['id' => '1', 'title' => 'First Post'],
    ],
    identityColumns: ['id'],
);
```

---

## IdentityRule

`Merql\Identity\IdentityRule` define as colunas usadas para criar chaves de linha.

```php
use Merql\Identity\IdentityRule;

$primary = IdentityRule::primary(['id']);
$natural = IdentityRule::natural(['option_name']);
$composite = IdentityRule::composite(['object_id', 'term_taxonomy_id']);
$content = IdentityRule::content(['name', 'value']);

$key = $natural->key(['option_name' => 'siteurl']);
// "siteurl"
```

### `forSchema(TableSchema $schema): IdentityRule` (estático)

Resolve a regra de identidade padrão a partir do esquema: chave primária, primeira chave exclusiva e, em seguida, alternativa baseada no conteúdo.

### `toArray(): array`

Serializa a regra como `['type' => string, 'columns' => list<string>]`.

### `fromArray(array $data): IdentityRule` (estático)

Hidrata uma regra a partir de dados serializados.

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` armazena substituições de identidade específicas de cada tabela.

```php
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;

$rules = new IdentityRuleSet([
    'wp_options' => IdentityRule::natural(['option_name']),
]);

$rule = $rules->ruleFor($schema);
```

### `with(string $table, IdentityRule $rule): IdentityRuleSet`

Retorna um novo conjunto de regras com uma substituição de tabela adicionada.

### `conflictsFor(string $table, IdentityRule $rule, array $rows): array`

Retorna entradas de `IdentityConflict` quando várias linhas geram a mesma chave para uma regra.

### `toArray(): array`

Serializa as regras de tabela como um array seguro para JSON.

### `fromArray(array $data): IdentityRuleSet` (estático)

Hidrata um conjunto de regras a partir de dados serializados.

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` persiste e carrega snapshots como arquivos JSON.

### `setDirectory(string $directory): void`

Define o diretório de armazenamento. O padrão é `.merql/snapshots`.

### `save(Snapshot $snapshot): void`

Grava um snapshot no disco.

### `load(string $name): Snapshot`

Carrega um snapshot pelo nome. Lança `SnapshotException` se não for encontrado.

### `exists(string $name): bool`

Verifica se um arquivo de snapshot existe.

### `delete(string $name): void`

Remove um arquivo de snapshot.

---

## Comparador

`Merql\Diff\Differ` compara dois snapshots e produz um conjunto de alterações.

### `diff(Snapshot $base, Snapshot $current): Changeset`

```php
use Merql\Diff\Differ;

$differ = new Differ();
$changeset = $differ->diff($baseSnapshot, $currentSnapshot);
```

### `valuesEqual(mixed $a, mixed $b): bool` (estático)

Compara dois valores convertendo-os em strings. NULL é tratado como um valor distinto: `NULL === NULL` é verdadeiro, `NULL === ''` é falso.

---

## Conjunto de alterações

`Merql\Diff\Changeset` é uma coleção somente leitura de operações no nível da linha.

```php
$changeset->inserts();             // list<RowInsert>
$changeset->updates();             // list<RowUpdate>
$changeset->deletes();             // list<RowDelete>
$changeset->isEmpty();             // bool
$changeset->count();               // int: total operations
$changeset->forTable('posts');     // Changeset: filtered to one table
```

---

## RowInsert, RowUpdate, RowDelete

Objetos de valor somente leitura que representam alterações individuais em linhas.

### RowInsert

```php
$insert->table;                    // string
$insert->rowKey;                   // string
$insert->values;                   // array<string, mixed>
```

### RowUpdate

```php
$update->table;                    // string
$update->rowKey;                   // string
$update->columnDiffs;              // list<ColumnDiff>
$update->fullRow;                  // array<string, mixed>: complete current row
$update->columnDiffMap();          // array<string, ColumnDiff>: keyed by column name
```

### RowDelete

```php
$delete->table;                    // string
$delete->rowKey;                   // string
$delete->oldValues;                // array<string, mixed>: values before deletion
```

---

## ColumnDiff

`Merql\Diff\ColumnDiff` representa uma única alteração de coluna em uma atualização de linha.

```php
$diff->column;                     // string
$diff->oldValue;                   // mixed
$diff->newValue;                   // mixed
```

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` é o algoritmo central de mesclagem.

### `__construct(?CellMergeConfig $cellMergeConfig = null)`

Passe um `CellMergeConfig` para ativar a mesclagem no nível da célula para colunas TEXT e JSON.

```php
use Merql\Merge\ThreeWayMerge;
use Merql\CellMerge\CellMergeConfig;

// Default: column-level merge only.
$merge = new ThreeWayMerge();

// With cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
```

### `merge(Snapshot $base, Snapshot $ours, Snapshot $theirs): MergeResult`

Realiza uma mesclagem de três vias. Calcula internamente os conjuntos de alterações (da base para o nosso lado e da base para o lado deles) e os mescla.

```php
$result = $merge->merge($baseSnapshot, $oursSnapshot, $theirsSnapshot);
```

### `patch(Snapshot $base, Snapshot $changes): MergeResult`

Atalho para mesclagem de duas vias. Equivalente a `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

Retorna `SchemaException[]` para quaisquer diferenças de esquema detectadas durante a última mesclagem (colunas adicionadas ou removidas entre snapshots).

---

## MergeResult

`Merql\Merge\MergeResult` é um objeto de valor somente leitura que contém o resultado da mesclagem.

```php
$result->isClean();                // bool: no conflicts
$result->operations();             // list<MergeOperation>
$result->conflicts();              // list<Conflict>
$result->operationCount();         // int
$result->conflictCount();          // int
$result->baseSnapshot();           // ?Snapshot
$result->schemaMismatches();       // list<SchemaException>
$result->hasSchemaMismatches();    // bool
```

---

## MergePlan

`Merql\Plan\MergePlan` é um modelo de revisão serializável para uma mesclagem.

```php
$plan->id;                        // string
$plan->baseSnapshot;              // string
$plan->oursSnapshot;              // string
$plan->theirsSnapshot;            // string
$plan->summary;                   // MergePlanSummary
$plan->changeGroups;              // list<MergePlanChangeGroup>
$plan->operations;                // list<MergePlanOperation>
$plan->conflicts;                 // list<MergePlanConflict>
$plan->schemaMismatches;          // list<string>
$plan->hash;                      // string
$plan->metadata;                  // array<string, mixed>
```

Use `MergePlanSerializer::toJson()` e `::fromJson()` para garantir roundtrips estáveis de JSON.

## Seleções

`ChangeGroupSelection` armazena IDs de grupos de alterações preparados. Converta-o em um `OperationSelection` ao criar resultados de mesclagem selecionados ou planos de reversão.

```php
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;

$selection = ChangeGroupSelection::fromIds([$groupId]);
$operationSelection = $selection->toOperationSelection($plan);

$selected = (new SelectedMergeResultFactory())
    ->fromOperationSelection($plan, $operationSelection);
```

## RollbackPlan

`Merql\Rollback\RollbackPlan` armazena operações inversas e expectativas de divergência para uma aplicação selecionada.

```php
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Rollback\RollbackPlanSerializer;

$rollback = (new RollbackPlanBuilder())->build(
    'rollback-1',
    $plan,
    $operationSelection,
    $liveBeforeRows,
);

$json = RollbackPlanSerializer::toJson($rollback);
```

Use `RollbackDrift` antes de aplicar a reversão e, em seguida, `RollbackPlanApplier` para executar as operações inversas.

---

## MergeOperation

`Merql\Merge\MergeOperation` é uma única operação resolvida da mesclagem.

```php
$op->type;                         // string: 'insert' | 'update' | 'delete'
$op->table;                        // string
$op->rowKey;                       // string
$op->values;                       // array<string, mixed>
$op->source;                       // string: 'ours' | 'theirs' | 'merged'
```

Constantes de tipo:

```php
MergeOperation::TYPE_INSERT;       // 'insert'
MergeOperation::TYPE_UPDATE;       // 'update'
MergeOperation::TYPE_DELETE;       // 'delete'
```

---

## Conflito

`Merql\Merge\Conflict` representa um conflito de mesclagem não resolvido.

```php
$conflict->table();                // string
$conflict->rowKey();               // string
$conflict->type();                 // string: 'update_update' | 'update_delete' | 'delete_update' | 'insert_insert'
$conflict->column();               // ?string (null for structural conflicts)
$conflict->oursValue();            // mixed
$conflict->theirsValue();          // mixed
$conflict->baseValue();            // mixed
$conflict->primaryKey(['id']);     // array<string, string>: decoded from rowKey
```

### Tipos de conflito

| Tipo | Significado |
|---|---|
| `update_update` | Ambos os lados alteraram a mesma coluna para valores diferentes |
| `update_delete` | Nosso lado atualizou uma linha que o lado deles excluiu |
| `delete_update` | Nosso lado excluiu uma linha que o lado deles atualizou |
| `insert_insert` | Ambos os lados inseriram uma linha com a mesma chave primária |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` aplica uma política de resolução a todos os conflitos no resultado da mesclagem.

### `resolve(MergeResult $result, ConflictPolicy $policy): MergeResult` (estático)

Retorna um novo `MergeResult` com os conflitos resolvidos de acordo com a política.

```php
use Merql\Merge\ConflictResolver;
use Merql\Merge\ConflictPolicy;

// Accept all "theirs" values when conflicts arise.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

// Accept all "ours" values.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::OursWins);

// Return unchanged (for manual inspection).
$resolved = ConflictResolver::resolve($result, ConflictPolicy::Manual);
```

---

## ConflictPolicy

`Merql\Merge\ConflictPolicy` é um enum baseado com três casos.

```php
use Merql\Merge\ConflictPolicy;

ConflictPolicy::OursWins;          // Resolve all conflicts in favor of "ours"
ConflictPolicy::TheirsWins;        // Resolve all conflicts in favor of "theirs"
ConflictPolicy::Manual;            // Leave conflicts unresolved for manual handling
```

---

## ColumnMerge

`Merql\Merge\ColumnMerge` executa a lógica de mesclagem por coluna para uma única linha. Usado internamente por `ThreeWayMerge`.

### `merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array` (estático)

Retorna `['values' => array, 'conflicts' => list<Conflict>]`.

```php
use Merql\Merge\ColumnMerge;

$result = ColumnMerge::merge(
    'posts',
    '42',
    ['title' => 'Hello', 'status' => 'draft'],     // base
    ['title' => 'Hello', 'status' => 'published'],  // ours
    ['title' => 'New Title', 'status' => 'draft'],   // theirs
);

// $result['values'] = ['title' => 'New Title', 'status' => 'published']
// $result['conflicts'] = [] (clean merge)
```

---

## Aplicador

`Merql\Apply\Applier` executa um resultado de mesclagem em um banco de dados.

### `__construct(DatabaseConnection $connection, ?Driver $driver = null)`

### `apply(MergeResult $result, ?Snapshot $base = null): ApplyResult`

Executa todas as operações em uma única transação. Reverte a transação em caso de erro e o informa em `ApplyResult::errors()` em vez de lançar uma exceção. Lança `ConflictException` se o resultado tiver conflitos não resolvidos.

```php
use Merql\Apply\Applier;

$applier = new Applier($connection);
$applied = $applier->apply($result);

echo "Rows affected: {$applied->rowsAffected()}\n";
if ($applied->hasErrors()) {
    print_r($applied->errors());
}
```

A ordem das chaves estrangeiras é gerenciada automaticamente: as inserções são ordenadas conforme as dependências, e as exclusões, na ordem inversa das dependências.

---

## ApplyResult

`Merql\Apply\ApplyResult` é um objeto de valor somente leitura retornado pelo aplicador.

```php
$applied->rowsAffected();         // int
$applied->errors();               // list<string>
$applied->hasErrors();            // bool
```

Após uma aplicação revertida, `rowsAffected()` ainda contabiliza as instruções executadas antes da falha ([#4](https://github.com/inline0/merql/issues/4)). Confira `hasErrors()` antes de confiar na contagem.

---

## DryRun

`Merql\Apply\DryRun` gera SQL legível por humanos sem executar nada.

### `generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array` (estático)

Retorna uma lista de strings SQL com os valores inseridos diretamente (apenas para exibição).

```php
use Merql\Apply\DryRun;

$statements = DryRun::generate($result);

foreach ($statements as $sql) {
    echo "{$sql};\n";
}

// Output:
// INSERT INTO `posts` (`id`, `title`) VALUES ('10', 'New Post');
// UPDATE `posts` SET `title` = 'Updated' WHERE `id` = '5';
// DELETE FROM `comments` WHERE `id` = '3';
```

---

## SqlGenerator

`Merql\Apply\SqlGenerator` gera instruções SQL parametrizadas. É usado internamente tanto por `Applier` quanto por `DryRun`.

### `generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array` (estático)

Retorna `list<array{sql: string, params: list<scalar|null>}>`.

```php
use Merql\Apply\SqlGenerator;

$statements = SqlGenerator::generate($result);

foreach ($statements as $stmt) {
    $connection->execute($stmt['sql'], $stmt['params']);
}
```

---

## CellMergeConfig

`Merql\CellMerge\CellMergeConfig` configura estratégias de mesclagem no nível da célula para colunas ou tipos de coluna específicos.

### `auto(): self` (estático)

Fábrica de conveniência que atribui `TextCellMerger` a colunas TEXT/LONGTEXT e `JsonCellMerger` a colunas JSON.

```php
use Merql\CellMerge\CellMergeConfig;

$config = CellMergeConfig::auto();
```

### `forColumn(string $column, CellMerger $merger): self`

Atribui um mesclador a uma coluna específica. Use `table.column` para nomes qualificados ou apenas `column` para todas as tabelas.

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\TextCellMerger;

$config = (new CellMergeConfig())
    ->forColumn('posts.content', new TextCellMerger())
    ->forColumn('description', new TextCellMerger());
```

### `forType(string $typePattern, CellMerger $merger): self`

Atribui um mesclador a todas as colunas que correspondem a um padrão de tipo (correspondência de substring sem distinção entre maiúsculas e minúsculas).

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;

$config = (new CellMergeConfig())
    ->forType('json', new JsonCellMerger());
```

### `getMerger(string $table, string $column, string $columnType = ''): CellMerger`

Busca o mesclador para uma determinada tabela, coluna e tipo. Prioridade: nome de coluna qualificado, nome de coluna não qualificado, padrão de tipo, padrão (opaco).

---

## CellMerger (interface)

`Merql\CellMerge\CellMerger` é a interface para estratégias de mesclagem no nível da célula.

```php
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;

interface CellMerger
{
    public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
}
```

São fornecidas três implementações integradas.

### TextCellMerger

`Merql\CellMerge\TextCellMerger` mescla textos com várias linhas usando uma comparação de três vias, linha por linha (o mesmo algoritmo que git usa para o conteúdo de arquivos). Quando os dois lados editam linhas diferentes, a mesclagem é limpa. Quando os dois lados editam a mesma linha de maneiras diferentes, é informado um conflito.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
$result = $merger->merge(
    "line 1\nline 2\nline 3",
    "line 1\nline 2 modified\nline 3",
    "line 1\nline 2\nline 3 modified",
);
// $result->clean === true
// $result->value === "line 1\nline 2 modified\nline 3 modified"
```

### JsonCellMerger

`Merql\CellMerge\JsonCellMerger` mescla valores JSON comparando as chaves de nível superior de forma independente. Cada chave é mesclada usando a lógica de três vias. Objetos aninhados são comparados como strings JSON opacas (não são mesclados recursivamente).

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
$result = $merger->merge(
    '{"a": 1, "b": 2}',
    '{"a": 10, "b": 2}',        // changed "a"
    '{"a": 1, "b": 20}',         // changed "b"
);
// $result->clean === true
// $result->value === '{"a":10,"b":20}'
```

### OpaqueCellMerger

`Merql\CellMerge\OpaqueCellMerger` é o padrão. Trata os valores como strings opacas e sempre informa um conflito quando são diferentes.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` é o tipo de retorno de todos os mescladores de células.

```php
$result->clean;                    // bool: true if merge succeeded
$result->value;                    // mixed: merged value (or ours on conflict)
$result->conflicts;                // int: number of conflicts

// Factory methods.
CellMergeResult::resolved($value);           // clean merge
CellMergeResult::conflict($oursValue, $n);   // conflict with count
```

---

## TableSchema

`Merql\Schema\TableSchema` é uma representação somente leitura da estrutura da tabela.

```php
use Merql\Schema\TableSchema;

$schema = new TableSchema(
    name: 'posts',
    columns: ['id' => 'int', 'title' => 'varchar(255)', 'content' => 'text'],
    primaryKey: ['id'],
    uniqueKeys: [['slug']],
);

$schema->name;                     // string
$schema->columns;                  // array<string, string>: name to type
$schema->primaryKey;               // list<string>
$schema->uniqueKeys;               // list<list<string>>
$schema->hasPrimaryKey();          // bool
$schema->hasUniqueKeys();          // bool
$schema->columnNames();            // list<string>
```

---

## Sistema de drivers

merql usa um sistema de drivers plugável para abstrair operações específicas de cada banco de dados. Os drivers são detectados automaticamente pela identidade do driver da conexão.

### Driver (interface)

`Merql\Driver\Driver` define o contrato que todos os drivers de banco de dados implementam.

```php
interface Driver
{
    public function quoteIdentifier(string $name): string;
    public function listTables(DatabaseConnection $connection): array;
    public function readSchema(DatabaseConnection $connection, string $table): TableSchema;
    public function readForeignKeys(DatabaseConnection $connection): array;
    public function selectAll(string $table): string;
}
```

Implementações integradas: `MysqlDriver` e `SqliteDriver`.

### DriverFactory

`Merql\Driver\DriverFactory` detecta automaticamente o driver a partir de uma conexão.

```php
use Merql\Driver\DriverFactory;

$driver = DriverFactory::create($connection);
```

Registre um driver personalizado para outros bancos de dados:

```php
DriverFactory::register('pgsql', MyPostgresDriver::class);
```

---

## Filtros

### TableFilter

`Merql\Filter\TableFilter` inclui ou exclui tabelas usando padrões glob.

```php
use Merql\Filter\TableFilter;

$filter = TableFilter::exclude(['cache_*', 'sessions', 'temp_*']);
$filter = TableFilter::include(['posts', 'users', 'comments']);
```

### ColumnFilter

`Merql\Filter\ColumnFilter` remove colunas específicas dos snapshots.

```php
use Merql\Filter\ColumnFilter;

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

### RowFilter

`Merql\Filter\RowFilter` filtra linhas usando uma função predicado.

```php
use Merql\Filter\RowFilter;

$filter = RowFilter::create(function (string $table, array $row): bool {
    // Exclude trashed posts.
    if ($table === 'posts' && ($row['status'] ?? '') === 'trash') {
        return false;
    }
    return true;
});
```

---

## Exceções

### SnapshotException

`Merql\Exceptions\SnapshotException` é lançado quando um snapshot não pode ser encontrado ou tem um nome inválido.

```php
SnapshotException::notFound('baseline', '/path/to/baseline.json');
```

### MergeException

`Merql\Exceptions\MergeException` é lançado quando falta um snapshot necessário para uma mesclagem.

```php
MergeException::snapshotRequired('base');
```

### ConflictException

`Merql\Exceptions\ConflictException` é lançado ao tentar aplicar um resultado de mesclagem com conflitos não resolvidos.

```php
ConflictException::unresolved(3);
// Message: "Cannot apply merge with 3 unresolved conflict(s)"
```

### SchemaException

`Merql\Exceptions\SchemaException` é lançado (ou coletado) quando os esquemas diferem entre snapshots.

```php
SchemaException::mismatch('posts', 'column "tags" exists in current but not in base');
```

Incompatibilidades de esquema não interrompem a mesclagem. Elas são coletadas e ficam disponíveis em `$result->schemaMismatches()` para inspeção.
