
# Načrti združevanja

`MergeResult` je izvedljivi izhod združitve. `MergePlan` je oblika tega izhoda, ki jo lahko pregledujete. Uporabite jo, kadar mora aplikacija prikazati, pripraviti, serializirati ali selektivno uveljaviti spremembe v podatkovni zbirki, preden jih zapiše v ciljno podatkovno zbirko.

```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()` naloži tri poimenovane posnetke in jih združi na ravni stolpcev, kot `Merql::merge()`. Če želite načrtovati združitev, ki uporablja združevanje na ravni celic za stolpca TEXT in JSON, rezultat ustvarite sami in ga posredujte v `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);
```

Načrt vsebuje:

- imena posnetkov, uporabljenih za njegovo izdelavo,
- povzetek operacij, konfliktov, neskladij shem, tabel in skupin,
- stabilne ID-je operacij,
- stabilne ID-je skupin sprememb,
- stolpce identitete vrstic in vrednosti identitete,
- vrednosti vrstic base, ours, theirs in result za vsako operacijo,
- neobvezne metapodatke gostiteljske aplikacije,
- zgoščeno vrednost načrta,
- različico sheme.

## Stabilni ID-ji

ID-ji operacij so deterministični za enako vsebino operacije. Izhajajo iz imena tabele, ključa vrstice, vrste operacije, vira operacije, spremenjenih stolpcev in vrednosti rezultata.

ID-ji skupin sprememb so deterministični za enako vsebino skupine. Privzeti graditelj ustvari eno skupino operacij za vsako operacijo. Gostiteljske aplikacije lahko iz seznama operacij sestavijo večje domensko specifične skupine in jih še vedno serializirajo v enaki obliki DTO.

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

## Oblika operacije

Vsak `MergePlanOperation` ohrani dovolj konteksta za pregled in povrnitev.

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

## Izbori

Uporabite `ChangeGroupSelection`, ko UI pripravi skupine. Pri uporabi ali ustvarjanju artefaktov za povrnitev ga pretvorite v `OperationSelection`.

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

Izbrani rezultat je čist `MergeResult`, ki vsebuje samo izbrane operacije. Posredujete ga lahko v `Merql::apply()` ali `Merql::applyGuarded()`.

## Izbira tega, kar cilju manjka

Načrt vsebuje operacije obeh strani, saj rezultat združitve pretvori base v združeno stanje. Če cilj že vsebuje eno stran, izberite samo operacije, ki jih še nima. Za cilj, ki vsebuje theirs, na primer za živo zbirko podatkov, ki prejema popravke iz peskovniške kopije, so to vse operacije z virom `ours` ter posodobitve `merged`, ki združujejo spremembe stolpcev z obeh strani:

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

Operacije z virom `theirs` in brisanja `merged` (vrstice, ki sta jih izbrisali obe strani) so v tem cilju že prisotni.

Načrt, ustvarjen iz združitve s konflikti, še vedno navaja operacijo za vsako vrstico v konfliktu, ki sta jo posodobili obe strani. Njene vrednosti rezultata v stolpcu v konfliktu vsebujejo vrednost ours, `SelectedMergeResultFactory` pa vrne čist rezultat za vsak izbor. Vrednosti `conflictState` operacije in skupine sprememb so tudi takrat `clean` ([#9](https://github.com/inline0/merql/issues/9)), zato sami preverite `$plan->conflicts`, kot je opisano zgoraj, ali razrešite konflikte z `ConflictResolver` in ustvarite načrt iz razrešenega rezultata z `MergePlanBuilder`. Za zapis izbora s predpogoji za žive vrstice glejte [Zaščitena uporaba](https://merql.inline0.dev/sl/docs/apply/guarded).

## Serializacija

Načrti združevanja in izbori imajo stabilno serializacijo JSON. Serializirani načrt shranite, kadar mora poznejši korak uporabe uporabiti točno pregledani nabor operacij.

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

## Vzdevki posnetkov

Načrti združevanja uporabljajo kanonična imena tabel. Če se fizična imena tabel med okolji razlikujejo, jih pred načrtovanjem zajemite pod istim kanoničnim imenom.

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

Shema, shranjena v posnetku, uporablja kanonično ime tabele, hkrati pa ohranja stolpce, ključe, stolpce identitete, ključe vrstic in filtre fizične tabele.
