
# Fletteplaner

En `MergeResult` er det eksekverbare resultat af en fletning. En `MergePlan` er en
form af resultatet, der kan inspiceres. Brug den, når et program skal vise,
forberede, serialisere eller anvende databaseændringer selektivt, før de skrives til
måldatabasen.

```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()` indlæser de tre navngivne øjebliksbilleder og fletter dem på kolonneniveau,
ligesom `Merql::merge()`. Hvis du vil planlægge en fletning, der bruger fletning på celleniveau for
TEXT- og JSON-kolonner, skal du selv oprette resultatet og sende det til
`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);
```

Planen indeholder:

- navnene på de øjebliksbilleder, der blev brugt til at oprette den,
- en oversigt over handlinger, konflikter, skemauoverensstemmelser, tabeller og grupper,
- stabile handlings-id'er,
- stabile id'er for ændringsgrupper,
- kolonner og værdier for rækkeidentitet,
- rækkeværdier for base, vores side, deres side og resultatet for hver handling,
- valgfrie metadata fra værtsprogrammet,
- en planhash,
- en skemaversion.

## Stabile id'er

Handlings-id'er er deterministiske for det samme handlingsindhold. De
udledes af tabelnavn, rækkenøgle, handlingstype, handlingskilde, ændrede
kolonner og resultatværdier.

Id'er for ændringsgrupper er deterministiske for det samme gruppeindhold. Standardbyggeren opretter én handlingsgruppe pr. handling. Værtsprogrammer kan oprette større domænespecifikke grupper ud fra handlingslisten og stadig serialisere dem
med den samme DTO-struktur.

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

## Handlingsstruktur

Hver `MergePlanOperation` indeholder nok kontekst til gennemgang og tilbagerulning.

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

## Udvalg

Brug `ChangeGroupSelection`, når UI forbereder grupper. Konvertér den til en
`OperationSelection`, når du anvender ændringer eller genererer artefakter til tilbagerulning.

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

Det valgte resultat er en ren `MergeResult`, der kun indeholder de valgte
handlinger. Det kan sendes til `Merql::apply()` eller `Merql::applyGuarded()`.

## Vælg det, et mål mangler

En plan indeholder handlingerne fra begge sider, fordi fletteresultatet ændrer base til den flettede tilstand. Hvis målet allerede indeholder den ene side, skal du kun vælge de handlinger, det endnu ikke har. For et mål, der indeholder deres side, såsom en live-database, der modtager ændringer fra en sandbox-kopi, omfatter det alle handlinger med kilden `ours` samt `merged`-opdateringer, der kombinerer begge siders kolonneændringer:

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

Handlinger med kilden `theirs` samt `merged`-sletninger (rækker, som begge sider har slettet) findes allerede i målet.

En plan oprettet ud fra en sammenfletning med konflikter viser stadig en operation for hver modstridende række, som begge sider har opdateret. Resultatværdierne indeholder vores værdi i den modstridende kolonne, og `SelectedMergeResultFactory` giver et rent resultat for ethvert valg. Operationens og ændringsgruppens `conflictState` viser dog stadig `clean` ([#9](https://github.com/inline0/merql/issues/9)), så kontrollér selv `$plan->conflicts` som ovenfor, eller løs konflikterne med `ConflictResolver`, og opret planen ud fra det løste resultat med `MergePlanBuilder`. Se [Guarded Apply](https://merql.inline0.dev/da/docs/apply/guarded) for at skrive valget med forudsætninger om de aktuelle rækker.

## Serialisering

Sammenfletningsplaner og valg har en stabil JSON-serialisering. Gem den serialiserede plan, når et senere anvendelsestrin skal bruge præcis det gennemgåede operationssæt.

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

## Aliasserede snapshots

Sammenfletningsplaner bruger kanoniske tabelnavne. Hvis de fysiske tabelnavne er forskellige mellem miljøer, skal du registrere dem under det samme kanoniske navn før planlægningen.

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

Skemaet i snapshottet bruger det kanoniske tabelnavn, mens den fysiske tabels kolonner, nøgler, identitetskolonner, rækkenøgler og filtre bevares.
