
# Unione a tre vie

Un’unione a tre vie confronta due insiemi di modifiche con un antenato comune e li combina in un unico risultato. merql applica questo algoritmo alle righe del database invece che alle righe dei file.

```php
use Merql\Merge\ThreeWayMerge;

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

echo $result->operationCount(); // resolved operations
echo $result->conflictCount();  // unresolved conflicts
```

## I tre stati

Ogni unione richiede esattamente tre snapshot:

- **Base** -- lo stato del database nel momento in cui i due branch si sono separati.
- **Nostro** -- un branch dopo le sue modifiche, per esempio una copia sandbox modificata.
- **Altrui** -- l’altro branch dopo le sue modifiche, per esempio il database live che ha continuato a funzionare.

Nostro e altrui devono essere acquisiti ciascuno dal database che contiene quel branch. Le regole di unione trattano i due lati allo stesso modo; i nomi determinano l’`source` di ogni operazione, il nome dei tipi di conflitto (`update_delete` significa che noi abbiamo aggiornato e gli altri hanno eliminato) e quale lato `ConflictPolicy::OursWins` e `TheirsWins` favoriscono.

```
         Base (snapshot at fork time)
        /                              \
   Ours (our changes)                   Theirs (their changes)
        \                              /
         --------- MERGE ----------
                     |
              Merged result
              + conflicts (if any)
```

Senza una base comune, non è possibile distinguere tra «aggiunto» e «invariato». Un confronto a due vie mostra che il lato A ha un valore e il lato B no, ma non può stabilire se A lo abbia aggiunto o B lo abbia eliminato. La base risponde a questa domanda.

## Come vengono calcolati i changeset

`Differ` confronta ogni snapshot con la base per produrre due changeset. Ogni changeset contiene tre tipi di operazioni:

| Base | Stato corrente | Operazione |
|---|---|---|
| La riga esiste | La riga esiste, stessa impronta | Nessuna modifica |
| La riga esiste | La riga esiste, impronta diversa | **UPDATE** (diff per colonna) |
| La riga esiste | La riga non esiste | **DELETE** |
| La riga non esiste | La riga esiste | **INSERT** |

Le impronte (SHA-256 hash del contenuto normalizzato della riga) offrono un percorso rapido. Se due impronte coincidono, la riga è invariata e non serve un confronto a livello di colonna.

```php
use Merql\Diff\Differ;

$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);
```

## Regole di unione

I due changeset vengono quindi uniti operazione per operazione. Questa tabella definisce il risultato per ogni combinazione:

| Nostro | Altrui | Risultato |
|---|---|---|
| Nessuna modifica | Nessuna modifica | Mantieni la base |
| Nessuna modifica | Aggiornata | Accetta altrui |
| Aggiornata | Nessuna modifica | Accetta nostro |
| Aggiornata (stesso valore) | Aggiornata (stesso valore) | Accetta (accordo tra entrambi) |
| Aggiornata (valore diverso) | Aggiornata (valore diverso) | **Conflitto** |
| Nessuna modifica | Eliminata | Accetta l’eliminazione |
| Eliminata | Nessuna modifica | Accetta l’eliminazione |
| Eliminata | Eliminata | Accetta l’eliminazione (accordo tra entrambi) |
| Aggiornata | Eliminata | **Conflitto** |
| Eliminata | Aggiornata | **Conflitto** |
| Inserita (solo nostro) | -- | Accetta l’inserimento |
| -- | Inserita (solo altrui) | Accetta l’inserimento |
| Inserita (stessa PK) | Inserita (stessa PK) | **Conflitto** |

Quando entrambi i lati aggiornano la stessa riga, merql non dichiara subito un conflitto. Passa all’unione a livello di colonna, confrontando ogni colonna singolarmente. Per i dettagli, consultate [Unione a livello di colonna](https://merql.inline0.dev/it/docs/merge/column-level).

## Uso della facade

La facade `Merql` opera su snapshot nominati salvati su disco. Chiamate `Merql::init()` con il database che contiene ciascuno stato prima di acquisire lo snapshot:

```php
use Merql\Connection;
use Merql\Merql;

$live    = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');

Merql::init($live);
Merql::snapshot('base');      // before the sandbox is copied from live

// ... copy live.sqlite to sandbox.sqlite, then both databases change ...

Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');

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

Non acquisite tutti e tre gli snapshot dallo stesso database, uno dopo l’altro. Lo snapshot altrui conterrebbe anche le nostre modifiche, quindi ogni riga inserita da noi sembrerebbe inserita da entrambi i lati e verrebbe segnalata come conflitto `insert_insert`.

`Merql::merge()`, `Merql::patch()` e `Merql::plan()` creano un `ThreeWayMerge` senza un `CellMergeConfig`, quindi uniscono solo a livello di colonna. Per l’unione a livello di cella, caricate gli snapshot e usate direttamente la classe, come mostrato di seguito.

## Uso diretto delle classi

Per un controllo maggiore, istanziate voi i componenti. `Snapshotter::capture()` non salva nulla, quindi conservate gli snapshot in memoria o salvateli con `SnapshotStore::save()`:

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\SnapshotStore;

$base   = (new Snapshotter($live))->capture('base');
// ... copy live to the sandbox, then both databases change ...
$ours   = (new Snapshotter($sandbox))->capture('ours');
$theirs = (new Snapshotter($live))->capture('theirs');

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

// Snapshots saved earlier, merged with cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
$result = $merge->merge(
    SnapshotStore::load('base'),
    SnapshotStore::load('ours'),
    SnapshotStore::load('theirs'),
);
```

## La scorciatoia patch

Quando solo un lato contiene modifiche (senza modifiche simultanee), usate `patch()`. È un’unione a due vie in cui nostro equivale alla base, quindi non possono verificarsi conflitti:

```php
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.
```

Tramite la facade:

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

`patch()` equivale a `merge($base, $base, $changes)`. Poiché nostro non si è mai discostato dalla base, ogni modifica altrui viene accettata senza conflitti.

## Rilevamento delle incompatibilità dello schema

Se tra gli snapshot sono state aggiunte o rimosse colonne da una tabella, l’unione procede comunque ma registra le incompatibilità. I tipi di colonna modificati non vengono confrontati e una colonna rinominata viene rilevata come una rimossa e una aggiunta; consultate [Validazione dello schema](https://merql.inline0.dev/it/docs/advanced/schema).

```php
$result = $merge->merge($base, $ours, $theirs);

if ($result->hasSchemaMismatches()) {
    foreach ($result->schemaMismatches() as $mismatch) {
        echo $mismatch->getMessage() . "\n";
    }
}
```

merql non esegue migrazioni automatiche dello schema. Le modifiche allo schema sono responsabilità di chi chiama la libreria. L’unione le segnala perché possiate decidere come procedere.

## MergeResult

L’oggetto risultato contiene tutto ciò che serve:

```php
$result->isClean();            // true if no conflicts
$result->operations();         // list of MergeOperation objects
$result->conflicts();          // list of Conflict objects
$result->operationCount();     // number of resolved operations
$result->conflictCount();      // number of unresolved conflicts
$result->hasSchemaMismatches(); // true if schemas differ
$result->baseSnapshot();       // the base snapshot used for the merge
```

Ogni `MergeOperation` ha un tipo (`insert`, `update` o `delete`), un nome di tabella, una chiave della riga, i valori delle colonne e un’origine (`ours`, `theirs` o `merged`). `merged` indica una riga aggiornata da entrambi i lati (con le modifiche combinate colonna per colonna) o eliminata da entrambi.

## Dove si applica un risultato di unione

Un risultato di unione descrive come trasformare la base nello stato unito. Le sue operazioni includono le modifiche di entrambi i lati: un inserimento effettuato da noi e uno effettuato dagli altri compaiono entrambi come inserimenti.

- `Merql::apply()` (o `Applier`) esegue ogni operazione sul database connesso. Si applica senza problemi a un database che contiene ancora la base, per esempio una copia appena creata dello stato di base. Su un database che contiene già le modifiche di un lato, l’inserimento di una riga già presente fallisce e la transazione viene annullata.
- Per riportare le nostre modifiche in un database che contiene già quelle altrui, per esempio promuovendo modifiche sandbox nell’ambiente live, applicate solo le operazioni mancanti: quelle con origine `ours` e gli aggiornamenti `merged`. Usate un [piano di unione](https://merql.inline0.dev/it/docs/merge/plans) per selezionarle e [Applicazione protetta](https://merql.inline0.dev/it/docs/apply/guarded) per scriverle con precondizioni sulle righe live.
