
# Planuri de îmbinare

Un `MergeResult` este rezultatul executabil al unei îmbinări. Un `MergePlan` este forma inspectabilă a acelui rezultat. Folosiți-l când o aplicație trebuie să afișeze, să pregătească, să serializeze sau să aplice selectiv modificări ale bazei de date înainte de a le scrie în baza de date țintă.

```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()` încarcă cele trei instantanee numite și le îmbină la nivel de coloană, la fel ca `Merql::merge()`. Pentru a planifica o îmbinare care folosește îmbinarea la nivel de celulă pentru coloanele TEXT și JSON, construiți rezultatul singur și transmiteți-l către `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);
```

Planul conține:

- numele instantaneelor folosite pentru a-l construi,
- un rezumat al operațiilor, conflictelor, nepotrivirilor de schemă, tabelelor și grupurilor,
- ID-uri de operații stabile,
- ID-uri stabile ale grupurilor de modificări,
- coloanele de identitate ale rândurilor și valorile de identitate,
- valorile rândurilor de bază, ale noastre, ale lor și rezultate pentru fiecare operație,
- metadate opționale ale aplicației gazdă,
- o amprentă a planului,
- o versiune a schemei.

## ID-uri stabile

ID-urile operațiilor sunt deterministe pentru același conținut al operației. Sunt derivate din numele tabelului, cheia rândului, tipul operației, sursa operației, coloanele modificate și valorile rezultate.

ID-urile grupurilor de modificări sunt deterministe pentru același conținut al grupului. Generatorul implicit creează un grup de operații pentru fiecare operație. Aplicațiile gazdă pot crea grupuri mai mari, specifice domeniului, din lista de operații și le pot serializa în continuare cu aceeași structură DTO.

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

## Structura operației

Fiecare `MergePlanOperation` păstrează suficient context pentru revizuire și revenire.

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

## Selecții

Folosiți `ChangeGroupSelection` când UI pregătește grupurile. Convertiți-l într-un `OperationSelection` la aplicare sau la generarea artefactelor de revenire.

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

Rezultatul selectat este un `MergeResult` curat, care conține doar operațiile selectate. Poate fi transmis către `Merql::apply()` sau `Merql::applyGuarded()`.

## Selectarea elementelor care lipsesc dintr-o țintă

Un plan conține operațiile ambelor părți, deoarece rezultatul îmbinării transformă baza în starea îmbinată. Când ținta conține deja una dintre părți, selectați doar operațiile care îi lipsesc. Pentru o țintă care conține starea lor, cum ar fi o bază de date activă care primește modificările unei copii de tip sandbox, acestea sunt toate operațiile cu sursa `ours`, plus actualizările `merged`, care combină modificările de coloană ale ambelor părți:

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

Operațiile cu sursa `theirs` și ștergerile `merged` (rânduri șterse de ambele părți) sunt deja prezente în acea țintă.

Un plan construit dintr-o îmbinare cu conflicte listează în continuare câte o operație pentru fiecare rând aflat în conflict pe care ambele părți l-au actualizat. Valorile rezultatului conțin valoarea noastră în coloana aflată în conflict, iar `SelectedMergeResultFactory` returnează un rezultat curat pentru orice selecție. Și atunci, `conflictState` operației și al grupului de modificări are în prezent valoarea `clean` ([#9](https://github.com/inline0/merql/issues/9)), așa că verificați singur `$plan->conflicts` ca mai sus sau rezolvați conflictele cu `ConflictResolver` și construiți planul din rezultatul rezolvat cu `MergePlanBuilder`. Consultați [Aplicare protejată](https://merql.inline0.dev/ro/docs/apply/guarded) pentru scrierea selecției cu condiții prealabile verificate pentru rândurile active.

## Serializare

Planurile de îmbinare și selecțiile au o serializare JSON stabilă. Stocați planul serializat dacă o etapă ulterioară de aplicare trebuie să folosească exact setul de operații revizuit.

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

## Instantanee cu aliasuri

Planurile de îmbinare folosesc nume canonice pentru tabele. Dacă numele fizice ale tabelelor diferă între medii, capturați-le sub același nume canonic înainte de planificare.

```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',
]));
```

Schema stocată în instantaneu folosește numele canonic al tabelului, păstrând coloanele, cheile, coloanele de identitate, cheile rândurilor și filtrele tabelului fizic.
