
# Tovejs sammenfletning

En trevejs sammenfletning sammenligner to sæt ændringer med en fælles forfader og samler dem i ét resultat. merql anvender algoritmen på databasens rækker i stedet for fillinjer.

```php
use Merql\Merge\ThreeWayMerge;

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

echo $result->operationCount(); // resolved operations
echo $result->conflictCount();  // unresolved conflicts
```

## De tre tilstande

Hver sammenfletning kræver præcis tre snapshots:

- **Base** -- databasetilstanden på det tidspunkt, hvor begge grene skiltes.
- **Vores** -- den ene gren efter dens ændringer, for eksempel en sandkassekopi, der er blevet redigeret.
- **Deres** -- den anden gren efter dens ændringer, for eksempel den aktive database, der fortsatte med at køre.

Vores og deres skal hver især registreres fra den database, der indeholder den pågældende gren. Sammenfletningsreglerne behandler begge sider ens; navnene afgør hver operations `source`, hvordan konflikttyper navngives (`update_delete` betyder, at vores opdaterede, og deres slettede), og hvilken side `ConflictPolicy::OursWins` og `TheirsWins` favoriserer.

```
         Base (snapshot at fork time)
        /                              \
   Ours (our changes)                   Theirs (their changes)
        \                              /
         --------- MERGE ----------
                     |
              Merged result
              + conflicts (if any)
```

Uden en fælles base kan du ikke skelne mellem "tilføjet" og "uændret". En sammenligning mellem to sider viser, at side A har en værdi, og side B ikke har den, men kan ikke afgøre, om A tilføjede den, eller B slettede den. Basen besvarer det spørgsmål.

## Sådan beregnes ændringssæt

`Differ` sammenligner hvert snapshot med basen for at oprette to ændringssæt. Hvert ændringssæt indeholder tre slags operationer:

| Base | Aktuel | Operation |
|---|---|---|
| Række findes | Række findes, samme fingeraftryk | Ingen ændring |
| Række findes | Række findes, forskelligt fingeraftryk | **UPDATE** (kolonnevis forskel) |
| Række findes | Række mangler | **DELETE** |
| Række mangler | Række findes | **INSERT** |

Fingeraftryk (SHA-256-hashværdier af normaliseret rækkeindhold) giver en hurtig genvej. Hvis to fingeraftryk stemmer overens, er rækken uændret, og en sammenligning på kolonneniveau er ikke nødvendig.

```php
use Merql\Diff\Differ;

$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);
```

## Regler for sammenfletning

De to ændringssæt sammenflettes derefter operation for operation. Tabellen viser resultatet for alle kombinationer:

| Vores | Deres | Resultat |
|---|---|---|
| Ingen ændring | Ingen ændring | Behold base |
| Ingen ændring | Opdateret | Acceptér deres |
| Opdateret | Ingen ændring | Acceptér vores |
| Opdateret (samme værdi) | Opdateret (samme værdi) | Acceptér (begge enige) |
| Opdateret (forskellig værdi) | Opdateret (forskellig værdi) | **Konflikt** |
| Ingen ændring | Slettet | Acceptér sletning |
| Slettet | Ingen ændring | Acceptér sletning |
| Slettet | Slettet | Acceptér sletning (begge enige) |
| Opdateret | Slettet | **Konflikt** |
| Slettet | Opdateret | **Konflikt** |
| Indsat (kun vores) | -- | Acceptér indsættelse |
| -- | Indsat (kun deres) | Acceptér indsættelse |
| Indsat (samme PK) | Indsat (samme PK) | **Konflikt** |

Når begge sider opdaterer den samme række, erklærer merql ikke straks en konflikt. Den går videre til sammenfletning på kolonneniveau og sammenligner hver kolonne for sig. Se [Sammenfletning på kolonneniveau](https://merql.inline0.dev/da/docs/merge/column-level) for flere oplysninger.

## Brug af facaden

`Merql`-facaden arbejder med navngivne snapshots, der er gemt på disken. Kald `Merql::init()` med databasen, der indeholder hver tilstand, før du tager snapshot af den:

```php
use Merql\Connection;
use Merql\Merql;

$live    = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');

Merql::init($live);
Merql::snapshot('base');      // before the sandbox is copied from live

// ... copy live.sqlite to sandbox.sqlite, then both databases change ...

Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');

$result = Merql::merge('base', 'ours', 'theirs');
```

Tag ikke alle tre snapshots fra én database efter hinanden. Deres vil dermed også indeholde vores ændringer, så hver række, vores indsatte, vil fremstå som indsat på begge sider og blive rapporteret som en `insert_insert`-konflikt.

`Merql::merge()`, `Merql::patch()` og `Merql::plan()` opretter en `ThreeWayMerge` uden en `CellMergeConfig`, så de sammenfletter kun på kolonneniveau. Ved sammenfletning på celleniveau skal du indlæse snapshots og bruge klassen direkte som vist nedenfor.

## Brug af klasserne direkte

Hvis du vil have mere kontrol, kan du selv instantiere komponenterne. `Snapshotter::capture()` gemmer ikke noget, så behold snapshots i hukommelsen, eller gem dem med `SnapshotStore::save()`:

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\SnapshotStore;

$base   = (new Snapshotter($live))->capture('base');
// ... copy live to the sandbox, then both databases change ...
$ours   = (new Snapshotter($sandbox))->capture('ours');
$theirs = (new Snapshotter($live))->capture('theirs');

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

// Snapshots saved earlier, merged with cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
$result = $merge->merge(
    SnapshotStore::load('base'),
    SnapshotStore::load('ours'),
    SnapshotStore::load('theirs'),
);
```

## Genvejen med patch

Når kun den ene side har ændringer (ingen samtidige ændringer), skal du bruge `patch()`. Dette er en tovejs sammenfletning, hvor vores er lig med basen, så konflikter er umulige:

```php
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.
```

Via facaden:

```php
$result = Merql::patch('base', 'changes');
```

`patch()` svarer til `merge($base, $base, $changes)`. Da vores aldrig afveg fra basen, accepteres alle ændringer fra deres uden konflikt.

## Registrering af skemaforskelle

Hvis kolonner er blevet tilføjet til eller fjernet fra en tabel mellem snapshots, fortsætter sammenfletningen stadig, men registrerer forskellene. Ændrede kolonnetyper sammenlignes ikke, og en omdøbt kolonne vises som én fjernet og én tilføjet kolonne; se [Skemavalidering](https://merql.inline0.dev/da/docs/advanced/schema).

```php
$result = $merge->merge($base, $ours, $theirs);

if ($result->hasSchemaMismatches()) {
    foreach ($result->schemaMismatches() as $mismatch) {
        echo $mismatch->getMessage() . "\n";
    }
}
```

merql migrerer ikke skemaer automatisk. Den, der kalder værktøjet, er ansvarlig for skemaændringer. Sammenfletningen rapporterer dem, så du kan beslutte, hvordan du vil fortsætte.

## MergeResult

Resultatobjektet indeholder alt, hvad du har brug for:

```php
$result->isClean();            // true if no conflicts
$result->operations();         // list of MergeOperation objects
$result->conflicts();          // list of Conflict objects
$result->operationCount();     // number of resolved operations
$result->conflictCount();      // number of unresolved conflicts
$result->hasSchemaMismatches(); // true if schemas differ
$result->baseSnapshot();       // the base snapshot used for the merge
```

Hver `MergeOperation` har en type (`insert`, `update` eller `delete`), et tabelnavn, en rækkenøgle, kolonneværdierne og en kilde (`ours`, `theirs` eller `merged`). `merged` markerer en række, som begge sider har opdateret (kombineret kolonne for kolonne) eller begge har slettet.

## Hvor et sammenfletningsresultat kan anvendes

Et sammenfletningsresultat beskriver, hvordan basen omdannes til den sammenflettede tilstand. Dets operationer indeholder ændringer fra begge sider: både en indsættelse fra vores side og en fra deres vises som indsættelser.

- `Merql::apply()` (eller `Applier`) udfører alle operationer mod den tilsluttede database. Resultatet kan anvendes uden problemer på en database, der stadig indeholder basen, for eksempel en ny kopi af basetilstanden. Hvis databasen allerede indeholder ændringer fra den ene side, mislykkes en indsættelse af en række, der findes i forvejen, og transaktionen rulles tilbage.
- Hvis du vil overføre vores ændringer til en database, der allerede indeholder deres, for eksempel ved at udrulle ændringer fra sandkassen til den aktive database, skal du kun anvende de operationer, som databasen mangler: dem med kilden `ours` og `merged`-opdateringer. Brug en [sammenfletningsplan](https://merql.inline0.dev/da/docs/merge/plans) til at vælge dem og [Guarded Apply](https://merql.inline0.dev/da/docs/apply/guarded) til at skrive dem med forudsætninger om de aktuelle rækker.
