
# Piani di unione

Un `MergeResult` è il risultato eseguibile di un’unione. Un `MergePlan` è la forma ispezionabile di quel risultato. Usatelo quando un’applicazione deve mostrare, preparare, serializzare o applicare selettivamente le modifiche al database prima di scriverle nel database di destinazione.

```php
use Merql\Merql;

$plan = Merql::plan(
    id: 'plan-2026-06-06',
    base: 'base',
    ours: 'ours',
    theirs: 'theirs',
    metadata: ['sandbox_id' => 'ow_123'],
);
```

`Merql::plan()` carica i tre snapshot nominati e li unisce a livello di colonna, come `Merql::merge()`. Per pianificare un’unione che usa l’unione a livello di cella per le colonne TEXT e JSON, create voi il risultato e passatelo a `MergePlanBuilder`:

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

$base = SnapshotStore::load('base');
$ours = SnapshotStore::load('ours');
$theirs = SnapshotStore::load('theirs');

$result = (new ThreeWayMerge(CellMergeConfig::auto()))->merge($base, $ours, $theirs);
$plan = (new MergePlanBuilder())->build('plan-2026-06-06', $base, $ours, $theirs, $result);
```

Il piano contiene:

- i nomi degli snapshot usati per crearlo,
- un riepilogo di operazioni, conflitti, incompatibilità dello schema, tabelle e gruppi,
- ID operazione stabili,
- ID gruppo di modifiche stabili,
- colonne di identità delle righe e valori di identità,
- valori delle righe di base, nostri, altrui e del risultato per ogni operazione,
- metadati facoltativi dell’applicazione host,
- un hash del piano,
- una versione dello schema.

## ID stabili

Gli ID operazione sono deterministici a parità di contenuto dell’operazione. Derivano dal nome della tabella, dalla chiave della riga, dal tipo di operazione, dall’origine dell’operazione, dalle colonne modificate e dai valori del risultato.

Gli ID dei gruppi di modifiche sono deterministici a parità di contenuto del gruppo. Il builder predefinito crea un gruppo di operazioni per ogni operazione. Le applicazioni host possono creare gruppi più ampi e specifici del dominio a partire dall’elenco delle operazioni, mantenendo lo stesso formato DTO nella serializzazione.

```php
foreach ($plan->changeGroups as $group) {
    echo $group->id . ': ' . implode(', ', $group->operationIds);
}
```

## Struttura dell’operazione

Ogni `MergePlanOperation` conserva il contesto necessario per la revisione e il rollback.

```php
$operation = $plan->operations[0];

$operation->id;              // stable operation ID
$operation->type;            // insert, update, delete
$operation->table;           // canonical table name
$operation->rowKey;          // encoded identity key
$operation->identityColumns; // columns used for identity
$operation->identityValues;  // decoded identity values
$operation->source;          // ours, theirs, or merged
$operation->baseRow;         // row in base snapshot, or null
$operation->oursRow;         // row in ours snapshot, or null
$operation->theirsRow;       // row in theirs snapshot, or null
$operation->resultValues;    // values the merge would write
$operation->changedColumns;  // changed result columns
```

## Selezioni

Usate `ChangeGroupSelection` quando UI prepara i gruppi. Convertitelo in un `OperationSelection` durante l’applicazione o la generazione degli artefatti di rollback.

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

$selection = ChangeGroupSelection::fromIds([
    $plan->changeGroups[0]->id,
]);

$operationSelection = $selection->toOperationSelection($plan);

$selectedResult = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    $operationSelection,
    SnapshotStore::load($plan->baseSnapshot),
);
```

Il risultato selezionato è un `MergeResult` senza conflitti che contiene solo le operazioni selezionate. Può essere passato a `Merql::apply()` o a `Merql::applyGuarded()`.

## Selezionare le operazioni mancanti nel database di destinazione

Un piano contiene le operazioni di entrambi i lati, perché il risultato dell’unione trasforma la base nello stato unito. Quando il database di destinazione contiene già un lato, selezionate solo le operazioni che gli mancano. Se contiene i dati altrui, per esempio un database live che riceve le modifiche di una copia sandbox, si tratta di tutte le operazioni con origine `ours`, più gli aggiornamenti `merged`, che combinano le modifiche alle colonne di entrambi i lati:

```php
use Merql\Plan\OperationSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

// Rows with an unresolved conflict stay out of the selection.
$conflicted = [];
foreach ($plan->conflicts as $conflict) {
    $conflicted[$conflict->table . "\0" . $conflict->rowKey] = true;
}

$ids = [];
foreach ($plan->operations as $operation) {
    $missing = $operation->source === 'ours'
        || ($operation->source === 'merged' && $operation->type === 'update');

    if ($missing && !isset($conflicted[$operation->table . "\0" . $operation->rowKey])) {
        $ids[] = $operation->id;
    }
}

$selected = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    OperationSelection::fromIds($ids),
    SnapshotStore::load($plan->baseSnapshot),
);
```

Le operazioni con origine `theirs` e le eliminazioni `merged` (righe eliminate da entrambi i lati) sono già presenti in quel database.

Un piano creato da un’unione con conflitti elenca comunque un’operazione per ogni riga in conflitto aggiornata da entrambi i lati. I valori del risultato contengono il nostro valore nella colonna in conflitto, e `SelectedMergeResultFactory` restituisce un risultato senza conflitti per qualsiasi selezione. Anche in questo caso, lo `conflictState` dell’operazione e del gruppo di modifiche indica attualmente `clean` ([#9](https://github.com/inline0/merql/issues/9)), quindi verificate voi `$plan->conflicts` come sopra oppure risolvete i conflitti con `ConflictResolver` e create il piano dal risultato risolto usando `MergePlanBuilder`. Consultate [Applicazione protetta](https://merql.inline0.dev/it/docs/apply/guarded) per scrivere la selezione con precondizioni sulle righe live.

## Serializzazione

I piani di unione e le selezioni hanno una serializzazione JSON stabile. Salvate il piano serializzato se un passaggio di applicazione successivo deve usare esattamente l’insieme di operazioni revisionato.

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

$json = MergePlanSerializer::toJson($plan);
$restored = MergePlanSerializer::fromJson($json);

$selectionJson = json_encode($selection->toArray(), JSON_THROW_ON_ERROR);
$selection = ChangeGroupSelection::fromArray(
    json_decode($selectionJson, true, flags: JSON_THROW_ON_ERROR),
);
```

## Snapshot con alias

I piani di unione usano nomi canonici per le tabelle. Se i nomi fisici delle tabelle differiscono tra ambienti, acquisitele con lo stesso nome canonico prima di creare il piano.

```php
use Merql\Snapshot\SnapshotStore;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

SnapshotStore::save($snapshotter->captureAliased('live', [
    'wp_posts' => 'wp_posts',
]));

SnapshotStore::save($snapshotter->captureAliased('sandbox', [
    'wp_onumia_abc_posts' => 'wp_posts',
]));
```

Lo schema salvato nello snapshot usa il nome canonico della tabella, preservando colonne, chiavi, colonne di identità, chiavi delle righe e filtri della tabella fisica.
