
# Trijų krypčių sujungimas

Trijų krypčių sujungimas palygina du pakeitimų rinkinius su bendra pradine versija ir sujungia juos į vieną rezultatą. merql šį algoritmą taiko duomenų bazės eilutėms, o ne failų eilutėms.

```php
use Merql\Merge\ThreeWayMerge;

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

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

## Trys būsenos

Kiekvienam sujungimui reikia tiksliai trijų momentinių vaizdų:

- **Bazinė versija** -- duomenų bazės būsena tuo metu, kai šakos išsiskyrė.
- **Mūsų pusė** -- viena šaka po pakeitimų, pavyzdžiui, redaguota smėliadėžės kopija.
- **Jų pusė** -- kita šaka po pakeitimų, pavyzdžiui, veikusi tiesioginė duomenų bazė.

Mūsų ir jų pusės turi būti užfiksuotos iš duomenų bazės, kurioje saugoma atitinkama šaka. Sujungimo taisyklės abi puses vertina vienodai; pavadinimai nurodo kiekvienos operacijos `source`, nustato konfliktų tipų pavadinimus (`update_delete` reiškia, kad mes atnaujinome, o jie ištrynė) ir tai, kurios pusės naudai sprendžiami `ConflictPolicy::OursWins` ir `TheirsWins`.

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

Neturint bendros bazinės versijos, neįmanoma atskirti „pridėta“ nuo „nepakeista“. Palyginus dvi versijas matyti, kad A pusė turi reikšmę, o B pusė jos neturi, bet neįmanoma nustatyti, ar A ją pridėjo, ar B ištrynė. Į šį klausimą atsako bazinė versija.

## Kaip apskaičiuojami pakeitimų rinkiniai

`Differ` palygina kiekvieną momentinį vaizdą su bazine versija ir sudaro du pakeitimų rinkinius. Kiekviename pakeitimų rinkinyje yra trijų tipų operacijos:

| Bazė | Dabartinė | Operacija |
|---|---|---|
| Eilutė yra | Eilutė yra, kontrolinės sumos sutampa | Nėra pakeitimų |
| Eilutė yra | Eilutė yra, kontrolinės sumos skiriasi | **ATNAUJINTI** (skirtumai pagal stulpelius) |
| Eilutė yra | Eilutės nėra | **IŠTRINTI** |
| Eilutės nėra | Eilutė yra | **ĮTERPTI** |

Kontrolinės sumos (SHA-256 normalizuotos eilutės turinio maišos) leidžia greitai patikrinti eilutes. Jei dvi kontrolinės sumos sutampa, eilutė nepakito ir nereikia lyginti atskirų stulpelių.

```php
use Merql\Diff\Differ;

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

## Sujungimo taisyklės

Tada abu pakeitimų rinkiniai sujungiami operacija po operacijos. Šioje lentelėje apibrėžtas rezultatas kiekvienam deriniui:

| Mūsų pakeitimai | Jų pakeitimai | Rezultatas |
|---|---|---|
| Nėra pakeitimų | Nėra pakeitimų | Palikti bazinę versiją |
| Nėra pakeitimų | Atnaujinta | Priimti jų pakeitimą |
| Atnaujinta | Nėra pakeitimų | Priimti mūsų pakeitimą |
| Atnaujinta (ta pati reikšmė) | Atnaujinta (ta pati reikšmė) | Priimti (abi pusės sutaria) |
| Atnaujinta (skirtinga reikšmė) | Atnaujinta (skirtinga reikšmė) | **Konfliktas** |
| Nėra pakeitimų | Ištrinta | Priimti ištrynimą |
| Ištrinta | Nėra pakeitimų | Priimti ištrynimą |
| Ištrinta | Ištrinta | Priimti ištrynimą (abi pusės sutaria) |
| Atnaujinta | Ištrinta | **Konfliktas** |
| Ištrinta | Atnaujinta | **Konfliktas** |
| Įterpta (tik mūsų pusėje) | -- | Priimti įterpimą |
| -- | Įterpta (tik jų pusėje) | Priimti įterpimą |
| Įterpta (tas pats PK) | Įterpta (tas pats PK) | **Konfliktas** |

Kai abi pusės atnaujina tą pačią eilutę, merql iškart nepaskelbia konflikto. Jis pereina prie stulpelių lygmens sujungimo ir lygina kiekvieną stulpelį atskirai. Daugiau informacijos rasite skyriuje [Sujungimas stulpelių lygmeniu](https://merql.inline0.dev/lt/docs/merge/column-level).

## Fasado naudojimas

`Merql` fasadas veikia su pavadintomis momentinėmis kopijomis, įrašytomis diske. Prieš darydami momentinę kopiją, iškvieskite `Merql::init()` su duomenų baze, kurioje yra kiekviena būsena:

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

Nedarykite visų trijų momentinių kopijų iš vienos duomenų bazės paeiliui. Tada jų kopijoje taip pat būtų mūsų pakeitimai, todėl kiekviena mūsų įterpta eilutė atrodytų kaip įterpta abiejose pusėse ir būtų pranešta apie `insert_insert` konfliktą.

`Merql::merge()`, `Merql::patch()` ir `Merql::plan()` sukuria `ThreeWayMerge` be `CellMergeConfig`, todėl sujungia tik stulpelių lygmeniu. Jei reikia sujungti langelių lygmeniu, įkelkite momentines kopijas ir naudokite pačią klasę, kaip parodyta toliau.

## Tiesioginis klasių naudojimas

Jei reikia daugiau kontrolės, sukurkite komponentų egzempliorius patys. `Snapshotter::capture()` nieko neišsaugo, todėl laikykite momentines kopijas atmintyje arba išsaugokite jas naudodami `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'),
);
```

## Greitasis pataisos būdas

Kai pakeitimų yra tik vienoje pusėje (nėra lygiagrečių pakeitimų), naudokite `patch()`. Tai dvipusis sujungimas, kuriame mūsų pusė sutampa su bazine versija, todėl konfliktų negali kilti:

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

Naudojant fasadą:

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

`patch()` atitinka `merge($base, $base, $changes)`. Kadangi mūsų pusė niekada nenukrypo nuo bazinės versijos, visi jų pakeitimai priimami be konfliktų.

## Schemos neatitikimų aptikimas

Jei tarp momentinių kopijų į lentelę buvo įtraukti arba iš jos pašalinti stulpeliai, sujungimas vis tiek vykdomas, tačiau užregistruojami neatitikimai. Pakeistų stulpelių tipai nelyginami, o pervadintas stulpelis laikomas vienu pašalintu ir vienu pridėtu stulpeliu; žr. [Schemos tikrinimas](https://merql.inline0.dev/lt/docs/advanced/schema).

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

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

merql automatiškai nemigruoja schemų. Už schemos pakeitimus atsako iškvietėjas. Sujungimo ataskaitoje jie nurodomi, kad galėtumėte nuspręsti, kaip elgtis toliau.

## MergeResult

Rezultato objekte pateikiama visa, ko reikia:

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

Kiekvienas `MergeOperation` turi tipą (`insert`, `update` arba `delete`), lentelės pavadinimą, eilutės raktą, stulpelių reikšmes ir šaltinį (`ours`, `theirs` arba `merged`). `merged` žymi eilutę, kurią atnaujino abi pusės (sujungus stulpelius atskirai), arba kurią abi pusės ištrynė.

## Kur galima pritaikyti sujungimo rezultatą

Sujungimo rezultatas aprašo, kaip bazinę versiją paversti sujungta būsena. Jo operacijose pateikiami abiejų pusių pakeitimai: mūsų pusėje atliktas įterpimas ir jų pusėje atliktas įterpimas abu rodomi kaip įterpimai.

- `Merql::apply()` (arba `Applier`) vykdo kiekvieną operaciją prijungtoje duomenų bazėje. Jį galima taikyti duomenų bazei, kurioje tebėra bazinė versija, pavyzdžiui, naujai bazinės būsenos kopijai. Jei taikysite duomenų bazei, kurioje jau yra vienos pusės pakeitimų, nepavyks įterpti jau esančios eilutės, o operacija bus atšaukta.
- Norėdami perkelti mūsų pakeitimus į duomenų bazę, kurioje jau yra jų pakeitimai, pavyzdžiui, perkelti smėliadėžės pakeitimus į veikiančią aplinką, pritaikykite tik trūkstamas operacijas: tas, kurių šaltinis yra `ours`, ir `merged` atnaujinimus. Pasirinkite jas naudodami [sujungimo planą](https://merql.inline0.dev/lt/docs/merge/plans), o įrašykite pasitelkę [apsaugotą pritaikymą](https://merql.inline0.dev/lt/docs/apply/guarded) su veikiančių eilučių išankstinėmis sąlygomis.
