
# Sammanslagningsplaner

En `MergeResult` är det körbara resultatet av en sammanslagning. En `MergePlan` är en granskningsbar version av resultatet. Använd den när en applikation behöver visa, förbereda, serialisera eller selektivt tillämpa databasändringar innan de skrivs till 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()` läser in de tre namngivna ögonblicksbilderna och slår samman dem på kolumnnivå, precis som `Merql::merge()`. Om du vill planera en sammanslagning som använder sammanslagning på cellnivå för kolumner av typen TEXT och JSON skapar du resultatet själv och skickar det till `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 innehåller:

- namnen på ögonblicksbilderna som användes för att skapa den,
- en sammanfattning av åtgärder, konflikter, schemamissmatchningar, tabeller och grupper,
- stabila åtgärds-ID:n,
- stabila ID:n för ändringsgrupper,
- radens identitetskolumner och identitetsvärden,
- basvärden, värden från vår sida och deras sida samt resultatvärden för varje åtgärd,
- valfria metadata från värdapplikationen,
- en planhash,
- en schemaversion.

## Stabila ID:n

Åtgärds-ID:n är deterministiska för samma åtgärdsinnehåll. De härleds från tabellnamn, radnyckel, åtgärdstyp, åtgärdskälla, ändrade kolumner och resultatvärden.

ID:n för ändringsgrupper är deterministiska för samma gruppinnehåll. Standardbyggaren skapar en åtgärdsgrupp per åtgärd. Värdapplikationer kan skapa större domänspecifika grupper från åtgärdslistan och ändå serialisera dem med samma DTO-form.

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

## Åtgärdens struktur

Varje `MergePlanOperation` innehåller tillräckligt med sammanhang för granskning och återställning.

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

## Urval

Använd `ChangeGroupSelection` när UI förbereder grupper. Omvandla den till en `OperationSelection` när ändringarna tillämpas eller återställningsartefakter skapas.

```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 valda resultatet är ett rent `MergeResult` som endast innehåller valda åtgärder. Det kan skickas till `Merql::apply()` eller `Merql::applyGuarded()`.

## Välja vad ett mål saknar

En plan innehåller båda sidornas operationer, eftersom sammanslagningsresultatet omvandlar basen
 till det sammanslagna tillståndet. När målet redan innehåller den ena sidan väljer du bara de
operationer som det ännu saknar. För ett mål som innehåller deras ändringar, till exempel en aktiv
databas som tar emot ändringar från en sandlåsekopia, är det varje operation med
källa `ours`, plus `merged`-uppdateringar, som kombinerar båda sidornas kolumnändringar:

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

Operationer med källa `theirs` och `merged`-borttagningar (rader som båda sidor har
tagit bort) finns redan i det målet.

En plan som byggts från en sammanslagning med konflikter listar fortfarande en operation för varje
konfliktrad som båda sidor har uppdaterat. Resultatvärdena innehåller vårt värde i den
konfliktande kolumnen, och `SelectedMergeResultFactory` ger ett rent resultat
för alla val. Operationens och ändringsgruppens `conflictState` visar för närvarande
`clean` även då ([#9](https://github.com/inline0/merql/issues/9)), så
kontrollera `$plan->conflicts` själv enligt ovan, eller lös konflikterna med
`ConflictResolver` och bygg planen från det lösta resultatet med
`MergePlanBuilder`. Se
[Skyddad tillämpning](https://merql.inline0.dev/sv/docs/apply/guarded) för att skriva urvalet med förvillkor för aktuella rader.

## Serialisering

Sammanslagningsplaner och urval har en stabil serialisering med JSON. Spara den serialiserade
planen när ett senare tillämpningssteg måste använda exakt den granskade operationsuppsättningen.

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

## Alias-ögonblicksbilder

Sammanslagningsplaner använder kanoniska tabellnamn. Om de fysiska tabellnamnen skiljer sig åt mellan
miljöer ska du samla in dem under samma kanoniska namn innan du skapar en plan.

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

Schemat som lagras i ögonblicksbilden använder det kanoniska tabellnamnet, samtidigt som
kolumner, nycklar, identitetskolumner, radnycklar och filter från den fysiska tabellen bevaras.
