
# Trīsvirzienu sapludināšana

Trīsvirzienu sapludināšanā salīdzina divas izmaiņu kopas ar kopīgu priekšteci un apvieno tās vienā rezultātā. merql piemēro šo algoritmu datubāzes rindām, nevis failu rindām.

```php
use Merql\Merge\ThreeWayMerge;

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

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

## Trīs stāvokļi

Katrai sapludināšanai nepieciešami tieši trīs momentuzņēmumi:

- **Bāze** -- datubāzes stāvoklis brīdī, kad abas atzari sadalījās.
- **Mūsu** -- viens atzars pēc tajā veiktajām izmaiņām, piemēram, rediģēta smilškastes kopija.
- **Viņu** -- otrs atzars pēc tajā veiktajām izmaiņām, piemēram, nepārtraukti darbojusies reāllaika datubāze.

Mūsu un viņu versija jāiegūst no datubāzes, kurā atrodas attiecīgais atzars. Sapludināšanas noteikumi abas puses aplūko vienādi; nosaukumi nosaka katras darbības `source`, konfliktu veidu nosaukumus (`update_delete` nozīmē, ka mēs atjauninājām, bet viņi izdzēsa) un to, kurai pusei par labu nosveras `ConflictPolicy::OursWins` un `TheirsWins`.

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

Bez kopīgas bāzes nav iespējams atšķirt “pievienots” no “nemainīts”. Divvirzienu salīdzinājumā redzams, ka A pusei ir vērtība, bet B pusei tās nav, taču nav iespējams noteikt, vai A to pievienoja vai B izdzēsa. Uz šo jautājumu atbild bāze.

## Kā tiek aprēķinātas izmaiņu kopas

`Differ` salīdzina katru momentuzņēmumu ar bāzi, lai izveidotu divas izmaiņu kopas. Katrā izmaiņu kopā ir trīs veidu darbības:

| Bāze | Pašreizējais stāvoklis | Darbība |
|---|---|---|
| Rinda pastāv | Rinda pastāv, pirkstu nospiedums nemainīgs | Bez izmaiņām |
| Rinda pastāv | Rinda pastāv, pirkstu nospiedums atšķiras | **UPDATE** (kolonnu atšķirības) |
| Rinda pastāv | Rindas nav | **DELETE** |
| Rindas nav | Rinda pastāv | **INSERT** |

Pirkstu nospiedumi (SHA-256 — normalizētas rindas satura jaucējvērtības) nodrošina ātru pārbaudi. Ja divas jaucējvērtības sakrīt, rinda nav mainījusies un kolonnu līmeņa salīdzināšana nav vajadzīga.

```php
use Merql\Diff\Differ;

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

## Sapludināšanas noteikumi

Pēc tam abas izmaiņu kopas tiek sapludinātas pa vienai darbībai. Šī tabula nosaka iznākumu visām iespējamām kombinācijām:

| Mūsu | Viņu | Rezultāts |
|---|---|---|
| Bez izmaiņām | Bez izmaiņām | Paturēt sākotnējo |
| Bez izmaiņām | Atjaunināts | Pieņemt viņu izmaiņas |
| Atjaunināts | Bez izmaiņām | Pieņemt mūsu izmaiņas |
| Atjaunināts (tā pati vērtība) | Atjaunināts (tā pati vērtība) | Pieņemt (abi piekrīt) |
| Atjaunināts (atšķirīga vērtība) | Atjaunināts (atšķirīga vērtība) | **Konflikts** |
| Bez izmaiņām | Dzēsts | Pieņemt dzēšanu |
| Dzēsts | Bez izmaiņām | Pieņemt dzēšanu |
| Dzēsts | Dzēsts | Pieņemt dzēšanu (abi piekrīt) |
| Atjaunināts | Dzēsts | **Konflikts** |
| Dzēsts | Atjaunināts | **Konflikts** |
| Ievietots (tikai mūsu pusē) | -- | Pieņemt ievietošanu |
| -- | Ievietots (tikai viņu pusē) | Pieņemt ievietošanu |
| Ievietots (tā pati PK) | Ievietots (tā pati PK) | **Konflikts** |

Ja abas puses atjaunina vienu un to pašu rindu, merql uzreiz nekonstatē konfliktu. Tas pāriet uz kolonnu līmeņa apvienošanu, neatkarīgi salīdzinot katru kolonnu. Sīkāku informāciju skatiet sadaļā [Apvienošana kolonnu līmenī](https://merql.inline0.dev/lv/docs/merge/column-level).

## Fasādes izmantošana

`Merql` fasāde darbojas ar nosauktiem momentuzņēmumiem, kas saglabāti diskā. Pirms momentuzņēmumu izveides izsauciet `Merql::init()`, norādot datubāzi, kurā ir katrs stāvoklis:

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

Neveidojiet visus trīs momentuzņēmumus secīgi no vienas datubāzes. Tad viņu momentuzņēmumā būtu arī mūsu izmaiņas, tāpēc katra mūsu ievietotā rinda abās pusēs izskatītos kā ievietota un tiktu uzrādīta kā `insert_insert` konflikts.

`Merql::merge()`, `Merql::patch()` un `Merql::plan()` izveido `ThreeWayMerge` bez `CellMergeConfig`, tāpēc apvienošana notiek tikai kolonnu līmenī. Lai apvienotu šūnu līmenī, ielādējiet momentuzņēmumus un izmantojiet tieši šo klasi, kā parādīts tālāk.

## Tieša klašu izmantošana

Lai iegūtu lielāku kontroli, izveidojiet komponentu instances paši. `Snapshotter::capture()` neko nesaglabā, tāpēc paturiet momentuzņēmumus atmiņā vai saglabājiet tos ar `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'),
);
```

## Īsceļš ar labojumu kopu

Ja izmaiņas ir tikai vienā pusē (nav vienlaicīgu izmaiņu), izmantojiet `patch()`. Tā ir divvirzienu apvienošana, kurā mūsu puse sakrīt ar sākotnējo stāvokli, tāpēc konflikti nav iespējami:

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

Ar fasādi:

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

`patch()` ir līdzvērtīgs `merge($base, $base, $changes)`. Tā kā mūsu puse nekad nav atšķīrusies no sākotnējā stāvokļa, visas viņu izmaiņas tiek pieņemtas bez konfliktiem.

## Shēmas neatbilstību noteikšana

Ja starp momentuzņēmumiem tabulai pievienotas vai no tās noņemtas kolonnas, apvienošana joprojām notiek, taču neatbilstības tiek reģistrētas. Mainīti kolonnu tipi netiek salīdzināti, bet pārdēvēta kolonna tiek uzrādīta kā viena noņemta un viena pievienota kolonna; skatiet [Shēmas validācija](https://merql.inline0.dev/lv/docs/advanced/schema).

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

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

merql shēmas nemigrē automātiski. Par shēmas izmaiņām atbild izsaucējs. Apvienošanas rezultātā tās tiek uzrādītas, lai jūs varētu izlemt, kā rīkoties tālāk.

## MergeResult

Rezultāta objektā ir viss nepieciešamais:

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

Katram `MergeOperation` ir tips (`insert`, `update` vai `delete`), tabulas nosaukums, rindas atslēga, kolonnu vērtības un avots (`ours`, `theirs` vai `merged`). `merged` apzīmē rindu, ko abas puses atjauninājušas (apvienojot izmaiņas katrā kolonnā atsevišķi), vai rindu, ko abas puses dzēsušas.

## Kur var izmantot apvienošanas rezultātu

Apvienošanas rezultātā aprakstīts, kā no sākotnējā stāvokļa iegūt apvienoto stāvokli. Tās darbības ietver abu pušu izmaiņas: gan mūsu, gan viņu ievietotā rinda tiek iekļauta kā ievietojama.

- `Merql::apply()` (vai `Applier`) izpilda katru darbību pievienotajā datubāzē. To var bez problēmām lietot datubāzē, kurā joprojām ir sākotnējais stāvoklis, piemēram, jaunā sākotnējā stāvokļa kopijā. Ja datubāzē jau ir vienas puses izmaiņas, esošas rindas ievietošana neizdosies un transakcija tiks atcelta.
- Lai iekļautu mūsu izmaiņas datubāzē, kurā jau ir viņu izmaiņas, piemēram, pārnestu smilškastes labojumus uz darbības vidi, lietojiet tikai tās darbības, kuru datubāzē trūkst: darbības ar avotu `ours` un `merged` atjaunināšanas. Izmantojiet [apvienošanas plānu](https://merql.inline0.dev/lv/docs/merge/plans), lai tās atlasītu, un [aizsargāto lietošanu](https://merql.inline0.dev/lv/docs/apply/guarded), lai tās ierakstītu, pārbaudot darbības vides rindu priekšnosacījumus.
