
# Riferimento PHP API

Riferimento completo per tutte le classi e i metodi pubblici di merql.

## Merql (facade)

`Merql\Merql` è il punto di accesso statico per le operazioni comuni. Avvolge le classi di livello inferiore e gestisce automaticamente la persistenza degli snapshot.

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

Inizializza la facade con una connessione al database. Deve essere chiamata prima
di `snapshot()`, `apply()` o `applyGuarded()`; chiamala di nuovo per passare da un database all'altro,
per esempio per acquisire snapshot di ours e theirs da due copie diverse. Senza una
`$driver`, chiama `DriverFactory::create()`, che genera un'eccezione per motori diversi da
MySQL e SQLite, a meno che non sia registrato un driver. Passa un driver per ignorare il rilevamento automatico oppure un insieme di regole di identità per specificare l'identità delle righe per tabella durante l'acquisizione dello snapshot.

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

Acquisisce lo stato attuale del database e lo salva su disco. Passa un array di nomi di tabella per acquisire snapshot di tabelle specifiche, oppure omettilo per includerle tutte.

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

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

Carica due snapshot con nome e calcola il changeset tra loro. Non richiede `init()` perché opera sugli snapshot salvati.

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

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

Fusione a tre vie di tre snapshot con nome. Restituisce un risultato con operazioni senza conflitti ed eventuali conflitti. Esegue la fusione a livello di colonna: la facade crea `ThreeWayMerge` senza `CellMergeConfig`, quindi non viene applicata la fusione a livello di cella. Per questo, usate direttamente [`ThreeWayMerge`](#threewaymerge).

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

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

Unione a due vie: applica le modifiche alla base. Equivalente a `merge($base, $base, $changes)`. Produce sempre un risultato pulito perché non ci sono modifiche concorrenti. Solo a livello di colonna, come `merge()`.

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

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

Esegue un risultato di unione pulito sul database passato a `init()`. Richiede `init()`. Genera `ConflictException` se restano conflitti irrisolti.

Viene eseguita ogni operazione del risultato, quindi il risultato completo di `merge()` si applica correttamente solo a un database che conserva ancora lo stato di base. Se il database contiene già uno dei due lati, applicate una selezione delle operazioni che gli mancano; consultate [Applicazione protetta](https://merql.inline0.dev/it/docs/apply/guarded).

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

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

Crea un piano di unione pronto per UI a partire da tre snapshot con nome. Il piano include ID operazione stabili, ID dei gruppi di modifiche, contesto delle operazioni, conflitti, incompatibilità dello schema, un riepilogo, metadati e un hash del piano. L'unione è a livello di colonna, come in `merge()`; passa un risultato unito a livello di cella a `MergePlanBuilder::build()` per un'unione a livello di cella.

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

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

Applica un risultato di unione pulito con precondizioni ottimistiche sulle righe correnti. Gli aggiornamenti e le eliminazioni richiedono che le righe correnti corrispondano a `$expectedLive`; per gli inserimenti è necessario che l'identità di destinazione non esista già. `$expectedLive` è uno snapshot del database di destinazione (quello di theirs, quando si promuove ours nel database da cui è stato acquisito theirs). Qualsiasi istruzione che non modifica righe provoca il rollback della transazione.

```php
use Merql\Snapshot\SnapshotStore;

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

### `reset(): void`

Azzera le istanze interne di connessione e Snapshotter. Viene usato principalmente nei test per ripristinare lo stato tra un'esecuzione e l'altra.

---

## Connessione

`Merql\Connection` crea adattatori di connessione con impostazioni predefinite sensate. Le connessioni PDO usano eccezioni in caso di errore, modalità di fetch associativa, prepared statement non simulati e valori recuperati convertiti in stringhe. L'adattatore mysqli normalizza i valori recuperati allo stesso formato stringa o 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`

Crea una connessione SQLite. Abilita automaticamente le chiavi esterne tramite `PRAGMA foreign_keys = ON`.

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

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

Connettiti usando qualsiasi stringa DSN PDO. merql include driver solo per `mysql` e `sqlite`; gli altri DSN aprono una connessione, ma `Merql::init()`, `Snapshotter` e gli applier generano `Unsupported database driver`, a meno che non venga registrato un driver con `DriverFactory::register()`.

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

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

Avvolge un'istanza PDO esistente.

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

Avvolge un'istanza mysqli esistente.

### Contratto dell'adattatore

Tutti gli adattatori implementano `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()` accettano parametri `list<scalar|null>` posizionali per i segnaposto `?`. `query()` restituisce `list<array<string, scalar|null>>`; i valori vengono normalizzati allo stesso formato stringa o null negli adattatori PDO e mysqli.

---

## Snapshotter

`Merql\Snapshot\Snapshotter` acquisisce lo stato del database. A differenza della facciata, offre il pieno controllo sui filtri e non salva automaticamente.

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

Il driver viene rilevato automaticamente dall'identità della connessione, se non specificato. Passa un `IdentityRuleSet` quando l'identità di riga specifica della tabella deve prevalere sui valori predefiniti dello schema.

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

Acquisisce lo stato corrente. Passa i nomi delle tabelle per limitare l'ambito. Passa oggetti filtro per escludere tabelle, colonne o righe.

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

Acquisisce le tabelle fisiche usando i nomi canonici delle tabelle. Usalo quando la stessa tabella logica ha nomi fisici diversi tra ambienti.

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

Lo snapshot risultante memorizza i nomi canonici delle tabelle preservando lo schema fisico, le colonne d'identità configurate, le chiavi di riga e i filtri.

### `fromData(string $name, array $tableData): Snapshot` (statico)

Crea uno snapshot da dati grezzi senza connessione al database. Utile per i test o quando si importano dati da un'altra origine.

```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` è un oggetto valore di sola lettura che rappresenta uno stato completo del database.

```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` contiene le righe, le impronte digitali e lo schema di una tabella.

```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` è un dato di input per `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` definisce le colonne usate per creare le chiavi di riga.

```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` (statico)

Determina la regola d'identità predefinita dallo schema: chiave primaria, prima chiave univoca, poi fallback sul contenuto.

### `toArray(): array`

Serializza la regola come `['type' => string, 'columns' => list<string>]`.

### `fromArray(array $data): IdentityRule` (statico)

Idrata una regola da dati serializzati.

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` memorizza sostituzioni dell'identità specifiche per tabella.

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

Restituisce un nuovo insieme di regole con una sostituzione per una tabella.

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

Restituisce le voci `IdentityConflict` quando più righe producono la stessa chiave per una regola.

### `toArray(): array`

Serializza le regole delle tabelle in un array compatibile con JSON.

### `fromArray(array $data): IdentityRuleSet` (statico)

Idrata un insieme di regole da dati serializzati.

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` salva e carica snapshot come file JSON.

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

Imposta la directory di archiviazione. Il valore predefinito è `.merql/snapshots`.

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

Scrive uno snapshot su disco.

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

Carica uno snapshot per nome. Genera `SnapshotException` se non viene trovato.

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

Verifica se esiste un file snapshot.

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

Rimuove un file snapshot.

---

## Differ

`Merql\Diff\Differ` confronta due snapshot e produce un insieme di modifiche.

### `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` (statico)

Confronta due valori convertendoli in stringhe. NULL viene trattato come valore distinto: `NULL === NULL` è vero, `NULL === ''` è falso.

---

## Changeset

`Merql\Diff\Changeset` è una raccolta di sola lettura di operazioni a livello di riga.

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

Oggetti valore di sola lettura che rappresentano singole modifiche di riga.

### 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` rappresenta una singola modifica di colonna all'interno di un aggiornamento di riga.

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

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` è l'algoritmo di unione principale.

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

Passa un `CellMergeConfig` per abilitare l'unione a livello di cella per le colonne 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`

Esegue un'unione a tre vie. Calcola internamente gli insiemi di modifiche (da base a ours, da base a theirs) e li unisce.

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

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

Scorciatoia per l'unione a due vie. Equivalente a `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

Restituisce `SchemaException[]` per ogni differenza di schema rilevata durante l'ultima unione (colonne aggiunte o rimosse tra snapshot).

---

## MergeResult

`Merql\Merge\MergeResult` è un oggetto valore di sola lettura che contiene l'esito dell'unione.

```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` è un modello di revisione serializzabile per un'unione.

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

Usa `MergePlanSerializer::toJson()` e `::fromJson()` per roundtrip JSON stabili.

## Selezioni

`ChangeGroupSelection` memorizza gli ID dei gruppi di modifiche preparati. Convertirlo in `OperationSelection` quando si creano risultati di unione selezionati o piani di rollback.

```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` memorizza operazioni inverse e aspettative di deriva per un'applicazione selezionata.

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

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

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

Usa `RollbackDrift` prima di applicare il rollback, poi `RollbackPlanApplier` per eseguire le operazioni inverse.

---

## MergeOperation

`Merql\Merge\MergeOperation` è una singola operazione risolta dell'unione.

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

Costanti di tipo:

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

---

## Conflitto

`Merql\Merge\Conflict` rappresenta un conflitto di unione irrisolto.

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

### Tipi di conflitto

| Tipo | Significato |
|---|---|
| `update_update` | Entrambi i lati hanno modificato la stessa colonna assegnandole valori diversi |
| `update_delete` | Ours ha aggiornato una riga che theirs ha eliminato |
| `delete_update` | Ours ha eliminato una riga che theirs ha aggiornato |
| `insert_insert` | Entrambi i lati hanno inserito una riga con la stessa chiave primaria |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` applica una politica di risoluzione a tutti i conflitti in un risultato di unione.

### `resolve(MergeResult $result, ConflictPolicy $policy): MergeResult` (statico)

Restituisce un nuovo `MergeResult` con i conflitti risolti secondo la politica.

```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` è un'enum con tre casi.

```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` esegue la logica di unione per colonna per una singola riga. Usato internamente da `ThreeWayMerge`.

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

Restituisce `['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)
```

---

## Applier

`Merql\Apply\Applier` applica un risultato di unione a un database.

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

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

Esegue tutte le operazioni in un'unica transazione. Esegue il rollback in caso di errore e lo segnala in `ApplyResult::errors()` invece di generare un'eccezione. Genera `ConflictException` se il risultato contiene conflitti irrisolti.

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

L'ordine delle chiavi esterne viene gestito automaticamente: gli inserimenti sono ordinati in base alle dipendenze, le eliminazioni in ordine inverso.

---

## ApplyResult

`Merql\Apply\ApplyResult` è un oggetto valore di sola lettura restituito dall'applier.

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

Dopo un'applicazione con rollback, `rowsAffected()` conta ancora le istruzioni eseguite prima dell'errore ([#4](https://github.com/inline0/merql/issues/4)). Verifica `hasErrors()` prima di considerare attendibile il conteggio.

---

## DryRun

`Merql\Apply\DryRun` genera SQL leggibili senza eseguire alcuna operazione.

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

Restituisce un elenco di stringhe SQL con i valori interpolati (solo a scopo di visualizzazione).

```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` genera istruzioni SQL parametrizzate. Usato internamente sia da `Applier` sia da `DryRun`.

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

Restituisce `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 strategie di unione a livello di cella per colonne specifiche o tipi di colonna.

### `auto(): self` (statico)

Factory di utilità che assegna `TextCellMerger` alle colonne TEXT/LONGTEXT e `JsonCellMerger` alle colonne JSON.

```php
use Merql\CellMerge\CellMergeConfig;

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

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

Assegna un merger a una colonna specifica. Usa `table.column` per nomi qualificati o solo `column` per tutte le tabelle.

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

Assegna un merger a tutte le colonne che corrispondono a un modello di tipo (corrispondenza di sottostringa senza distinzione tra maiuscole e minuscole).

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

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

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

Cerca il merger per una determinata tabella, colonna e tipo. Priorità: nome di colonna qualificato, nome di colonna non qualificato, modello di tipo, valore predefinito (opaque).

---

## CellMerger (interfaccia)

`Merql\CellMerge\CellMerger` è l'interfaccia per le strategie di unione a livello di cella.

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

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

Sono fornite tre implementazioni integrate.

### TextCellMerger

`Merql\CellMerge\TextCellMerger` unisce testo su più righe usando un diff a tre vie, riga per riga (lo stesso algoritmo che git usa per i contenuti dei file). Se i due lati modificano righe diverse, l'unione riesce senza conflitti. Se modificano la stessa riga in modo diverso, viene segnalato un conflitto.

```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` unisce valori JSON confrontando le chiavi di primo livello in modo indipendente. Ogni chiave viene unita con una logica a tre vie. Gli oggetti annidati vengono confrontati come stringhe JSON opache (senza unione ricorsiva).

```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` è il valore predefinito. Tratta i valori come stringhe opache e segnala sempre un conflitto quando differiscono.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` è il tipo restituito da tutti i merger di cella.

```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` è una rappresentazione di sola lettura della struttura di una tabella.

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

merql usa un sistema di driver collegabile per astrarre le operazioni specifiche del database. I driver vengono rilevati automaticamente dall'identità del driver della connessione.

### Driver (interfaccia)

`Merql\Driver\Driver` definisce il contratto implementato da tutti i driver di database.

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

Implementazioni integrate: `MysqlDriver` e `SqliteDriver`.

### DriverFactory

`Merql\Driver\DriverFactory` rileva automaticamente il driver da una connessione.

```php
use Merql\Driver\DriverFactory;

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

Registra un driver personalizzato per altri database:

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

---

## Filtri

### TableFilter

`Merql\Filter\TableFilter` include o esclude tabelle usando modelli glob.

```php
use Merql\Filter\TableFilter;

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

### ColumnFilter

`Merql\Filter\ColumnFilter` rimuove colonne specifiche dagli snapshot.

```php
use Merql\Filter\ColumnFilter;

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

### RowFilter

`Merql\Filter\RowFilter` filtra le righe usando una funzione predicato.

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

---

## Eccezioni

### SnapshotException

`Merql\Exceptions\SnapshotException` viene generata quando uno snapshot non viene trovato o ha un nome non valido.

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

### MergeException

`Merql\Exceptions\MergeException` viene generata quando manca uno snapshot richiesto per un'unione.

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

### ConflictException

`Merql\Exceptions\ConflictException` viene generata quando si tenta di applicare un risultato di unione che contiene conflitti irrisolti.

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

### SchemaException

`Merql\Exceptions\SchemaException` viene generata (o raccolta) quando gli schemi differiscono tra snapshot.

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

Le incompatibilità dello schema non interrompono l'unione. Vengono raccolte e sono disponibili tramite `$result->schemaMismatches()` per l'analisi.
