
# Kolmen tilan yhdistäminen

Kolmen tilan yhdistämisessä verrataan kahta muutosjoukkoa yhteiseen kantatilaan ja yhdistetään ne yhdeksi tulokseksi. merql käyttää tätä algoritmia tiedostorivien sijaan tietokannan riveihin.

```php
use Merql\Merge\ThreeWayMerge;

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

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

## Kolme tilaa

Jokainen yhdistäminen edellyttää täsmälleen kolmea tilannevedosta:

- **Perusta** -- tietokannan tila siinä vaiheessa, kun haarat erkanivat toisistaan.
- **Meidän puolemme** -- yksi haara muutostensa jälkeen, esimerkiksi muokattu hiekkalaatikkokopio.
- **Heidän puolensa** -- toinen haara muutostensa jälkeen, esimerkiksi käynnissä pysynyt tuotantotietokanta.

Meidän ja heidän puolensa on kumpikin tallennettava tietokannasta, jossa kyseinen haara sijaitsee. Yhdistämissäännöt käsittelevät molempia puolia samalla tavalla; nimet määrittävät kunkin toiminnon `source`, ristiriitatyyppien nimet (`update_delete` tarkoittaa, että me päivitimme ja he poistivat) sekä sen, kumpaa puolta `ConflictPolicy::OursWins` ja `TheirsWins` suosivat.

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

Ilman yhteistä perustaa et voi erottaa lisäystä muuttumattomasta. Kahden tilan vertailussa nähdään, että puolella A on arvo ja puolelta B se puuttuu, mutta ei voida päätellä, lisäsikö A sen vai poistiko B sen. Perusta antaa vastauksen.

## Muutossarjojen laskeminen

`Differ` vertaa kumpaakin tilannevedosta perustaan ja tuottaa kaksi muutossarjaa. Kummassakin muutossarjassa on kolmenlaisia toimintoja:

| Perusta | Nykyinen | Toiminto |
|---|---|---|
| Rivi on olemassa | Rivi on olemassa, sama sormenjälki | Ei muutosta |
| Rivi on olemassa | Rivi on olemassa, eri sormenjälki | **UPDATE** (sarakkeittainen diff) |
| Rivi on olemassa | Rivi puuttuu | **DELETE** |
| Rivi puuttuu | Rivi on olemassa | **INSERT** |

Sormenjäljet (SHA-256 normalisoidun rivisisällön hajautusarvot) tarjoavat nopean käsittelypolun. Jos kaksi sormenjälkeä täsmää, rivi on muuttumaton eikä sarakekohtaista vertailua tarvita.

```php
use Merql\Diff\Differ;

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

## Yhdistämissäännöt

Sen jälkeen kaksi muutossarjaa yhdistetään toiminto kerrallaan. Tässä taulukossa esitetään lopputulos jokaiselle yhdistelmälle:

| Meidän puolemme | Heidän puolensa | Tulos |
|---|---|---|
| Ei muutosta | Ei muutosta | Säilytä perusta |
| Ei muutosta | Päivitetty | Hyväksy heidän muutoksensa |
| Päivitetty | Ei muutosta | Hyväksy meidän muutoksemme |
| Päivitetty (sama arvo) | Päivitetty (sama arvo) | Hyväksy (molemmat samaa mieltä) |
| Päivitetty (eri arvo) | Päivitetty (eri arvo) | **Ristiriita** |
| Ei muutosta | Poistettu | Hyväksy poisto |
| Poistettu | Ei muutosta | Hyväksy poisto |
| Poistettu | Poistettu | Hyväksy poisto (molemmat samaa mieltä) |
| Päivitetty | Poistettu | **Ristiriita** |
| Poistettu | Päivitetty | **Ristiriita** |
| Lisätty (vain meidän puolellamme) | -- | Hyväksy lisäys |
| -- | Lisätty (vain heidän puolellaan) | Hyväksy lisäys |
| Lisätty (sama PK) | Lisätty (sama PK) | **Ristiriita** |

Kun molemmat puolet päivittävät samaa riviä, merql ei ilmoita ristiriidasta heti. Se siirtyy saraketason yhdistämiseen ja vertaa jokaista saraketta erikseen. Katso lisätiedot kohdasta [saraketason yhdistäminen](https://merql.inline0.dev/fi/docs/merge/column-level).

## Käyttö julkisivun kautta

`Merql`-julkisivu käsittelee levylle tallennettuja nimettyjä tilannevedoksia. Kutsu `Merql::init()`-toimintoa kunkin tilan sisältävällä tietokannalla ennen tilannevedoksen ottamista:

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

Älä ota kaikkia kolmea tilannevedosta peräkkäin samasta tietokannasta. Heidän tilannevedoksensa sisältäisi silloin myös meidän muutoksemme, joten jokainen meidän lisäämämme rivi näyttäisi olevan lisätty molemmilla puolilla ja aiheuttaisi `insert_insert`-ristiriidan.

`Merql::merge()`, `Merql::patch()` ja `Merql::plan()` luovat `ThreeWayMerge`-tuloksen ilman `CellMergeConfig`-toimintoa, joten yhdistäminen tapahtuu vain saraketasolla. Solutason yhdistämistä varten lataa tilannevedokset ja käytä suoraan luokkaa, kuten alla.

## Luokkien suora käyttö

Voit hallita toimintaa tarkemmin luomalla komponentit itse. `Snapshotter::capture()` ei tallenna mitään, joten pidä tilannevedokset muistissa tai tallenna ne `SnapshotStore::save()`-toiminnolla:

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

## Pikakuvake korjaustiedoston käyttöön

Kun vain toisella puolella on muutoksia (samanaikaisia muokkauksia ei ole), käytä `patch()`-toimintoa. Kyseessä on kahden tilan yhdistäminen, jossa meidän puolemme vastaa perustaa, joten ristiriitoja ei voi syntyä:

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

Julkisivun kautta:

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

`patch()` vastaa toimintoa `merge($base, $base, $changes)`. Koska meidän puolemme ei koskaan eronnut perustasta, kaikki heidän muutoksensa hyväksytään ilman ristiriitoja.

## Skeemapoikkeamien tunnistus

Jos tauluun lisättiin tai siitä poistettiin sarakkeita tilannevedosten välillä, yhdistäminen jatkuu mutta poikkeamat kirjataan. Muuttuneita saraketyyppejä ei verrata, ja uudelleennimetty sarake näkyy yhtenä poistettuna ja yhtenä lisättynä; katso [skeeman validointi](https://merql.inline0.dev/fi/docs/advanced/schema).

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

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

merql ei siirrä skeemaa automaattisesti. Skeemamuutokset ovat kutsujan vastuulla. Yhdistäminen ilmoittaa niistä, jotta voit päättää, miten jatkat.

## MergeResult

Tulosolio sisältää kaiken tarvittavan:

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

Jokaisella `MergeOperation`-toiminnolla on tyyppi (`insert`, `update` tai `delete`), taulun nimi, rivin avain, sarakearvot ja lähde (`ours`, `theirs` tai `merged`). `merged` merkitsee riviä, jonka molemmat puolet päivittivät (sarakkeet yhdistettiin) tai jonka molemmat puolet poistivat.

## Yhdistämistuloksen käyttökohde

Yhdistämistulos kuvaa, miten perusta muutetaan yhdistetyksi tilaksi. Sen toiminnot sisältävät molempien puolten muutokset: sekä meidän että heidän tekemänsä lisäys näkyy lisäyksenä.

- `Merql::apply()` (tai `Applier`) suorittaa kaikki toiminnot yhdistetyssä tietokannassa. Se soveltuu sellaisenaan tietokantaan, jossa on edelleen perusta, kuten uuteen kopioon perustilasta. Jos tietokanta sisältää jo toisen puolen muutokset, olemassa olevan rivin lisäys epäonnistuu ja tapahtuma peruutetaan.
- Jos haluat tuoda meidän muutoksemme tietokantaan, jossa on jo heidän muutoksensa, esimerkiksi siirtää hiekkalaatikon muutokset käytössä olevaan ympäristöön, ota käyttöön vain toiminnot, joita tietokannasta puuttuu: lähteen `ours` toiminnot sekä `merged`-päivitykset. Valitse ne [yhdistämissuunnitelman](https://merql.inline0.dev/fi/docs/merge/plans) avulla ja kirjoita ne tietokantaan [Suojatun käyttöönoton](https://merql.inline0.dev/fi/docs/apply/guarded) avulla käyttämällä käytössä olevien rivien ennakkoehtoja.
