
# Rollback

Rollback-Pläne beschreiben, wie ausgewählte Zusammenführungsoperationen rückgängig gemacht werden,
nachdem sie angewendet wurden. Sie werden vor der Anwendung anhand der exakten Live-Zeilen erstellt,
die unmittelbar vor dem Schreibvorgang vorhanden sind.

Erstelle einen Rollback nicht allein aus dem Basis-Snapshot. Base ist der gemeinsame
Vorgänger, aber nicht zwingend der Live-Zustand, der überschrieben wird.

## Rollback-Plan erstellen

```php
use Merql\Plan\ChangeGroupSelection;
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Snapshot\SnapshotStore;

$selection = ChangeGroupSelection::fromIds([$plan->changeGroups[0]->id]);
$operationSelection = $selection->toOperationSelection($plan);
$expectedLive = SnapshotStore::load($plan->theirsSnapshot);

$liveBeforeRows = [];
foreach ($plan->operations as $operation) {
    if (!$operationSelection->contains($operation->id)) {
        continue;
    }

    $liveBeforeRows[$operation->id] = $expectedLive
        ->getTable($operation->table)
        ?->getRow($operation->rowKey);
}

$rollback = (new RollbackPlanBuilder())->build(
    'rollback-1',
    $plan,
    $operationSelection,
    $liveBeforeRows,
    metadata: ['promotion_id' => 'promotion-1'],
);
```

Der Plan speichert:

- die ID des Zusammenführungsplans,
- ausgewählte Operations-IDs,
- betroffene Tabellen,
- inverse Operationen,
- vorherige Zeilen,
- erwartete Zeilen danach,
- Metadaten,
- einen Rollback-Hash,
- eine Schema-Version.

## Abweichungen prüfen

Vergleiche vor dem Rollback die aktuellen Live-Zeilen mit den erwarteten Zeilen danach aus dem Rollback-Plan. Wenn sich seit der Übernahme eine Zeile geändert hat, wird standardmäßig abgebrochen.

```php
use Merql\Rollback\RollbackDrift;

$currentRows = [
    $operationId => $currentLiveRow,
];

$drifted = (new RollbackDrift())->driftedOperationIds($rollback, $currentRows);

if ($drifted !== []) {
    throw new RuntimeException('Rollback drifted: ' . implode(', ', $drifted));
}
```

Die Abweichungsprüfung vergleicht Zeilen-Fingerprints; die Spaltenreihenfolge spielt daher keine Rolle.

## Rollback anwenden

Wenn keine Abweichungen vorliegen, wende die inversen Operationen an.

```php
use Merql\Rollback\RollbackPlanApplier;

$applied = (new RollbackPlanApplier($connection))->apply($rollback);

if ($applied->hasErrors()) {
    foreach ($applied->errors() as $error) {
        echo $error . "\n";
    }
}
```

Rollback wendet ein normales `MergeResult` an, das aus inversen Operationen erstellt wurde. Es läuft
über denselben transaktionalen Anwendungspfad wie eine gewöhnliche Zusammenführung.

## Serialisierung

Speichere Rollback-Pläne dauerhaft, bevor du die vorwärtsgerichtete Zusammenführung anwendest. Dieses Artefakt
bildet die dauerhafte Grundlage für einen späteren Rollback.

```php
use Merql\Rollback\RollbackPlanSerializer;

$json = RollbackPlanSerializer::toJson($rollback);
$restored = RollbackPlanSerializer::fromJson($json);
```

Anwendungen sollten den serialisierten Plan zusammen mit dem Vorgang speichern, der den Live-Schreibvorgang ausgeführt hat,
einschließlich aller anwendungsspezifischen Audit-Metadaten.
