
# Scalanie trójstronne

Scalanie trójstronne porównuje dwa zestawy zmian ze wspólnym przodkiem i łączy je w jeden wynik. merql stosuje ten algorytm do wierszy bazy danych zamiast wierszy plików.

```php
use Merql\Merge\ThreeWayMerge;

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

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

## Trzy stany

Każde scalanie wymaga dokładnie trzech zrzutów:

- **Baza** -- stan bazy danych w chwili rozgałęzienia obu gałęzi.
- **Nasza strona** -- jedna z gałęzi po wprowadzeniu zmian, na przykład edytowana kopia piaskownicy.
- **Druga strona** -- druga gałąź po wprowadzeniu zmian, na przykład działająca baza danych, która nadal była używana.

Naszą stronę i drugą stronę należy zarejestrować każdą z bazy danych zawierającej daną gałąź. Reguły scalania traktują obie strony jednakowo; nazwy określają źródło każdej operacji `source`, sposób nazywania typów konfliktów (`update_delete` oznacza, że nasza strona zaktualizowała, a druga usunęła wiersz) oraz to, której stronie sprzyjają `ConflictPolicy::OursWins` i `TheirsWins`.

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

Bez wspólnej bazy nie da się odróżnić elementu „dodano” od „bez zmian”. Porównanie dwóch stron pokazuje, że strona A ma wartość, a strona B jej nie ma, ale nie pozwala ustalić, czy strona A ją dodała, czy strona B usunęła. Odpowiedź daje baza.

## Jak obliczane są zestawy zmian

`Differ` porównuje każdy zrzut z bazą, aby utworzyć dwa zestawy zmian. Każdy zestaw obejmuje trzy rodzaje operacji:

| Baza | Bieżący stan | Operacja |
|---|---|---|
| Wiersz istnieje | Wiersz istnieje, ten sam odcisk | Bez zmian |
| Wiersz istnieje | Wiersz istnieje, inny odcisk | **UPDATE** (różnice w poszczególnych kolumnach) |
| Wiersz istnieje | Brak wiersza | **DELETE** |
| Brak wiersza | Wiersz istnieje | **INSERT** |

Odciski (SHA-256 skróty znormalizowanej zawartości wiersza) zapewniają szybką ścieżkę. Jeśli dwa odciski są identyczne, wiersz pozostaje bez zmian i nie trzeba porównywać poszczególnych kolumn.

```php
use Merql\Diff\Differ;

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

## Reguły scalania

Następnie oba zestawy zmian są scalane operacja po operacji. Ta tabela określa wynik dla każdej kombinacji:

| Nasze | Ich | Wynik |
|---|---|---|
| Bez zmian | Bez zmian | Zachowaj bazę |
| Bez zmian | Zaktualizowano | Przyjmij ich zmiany |
| Zaktualizowano | Bez zmian | Przyjmij nasze zmiany |
| Zaktualizowano (ta sama wartość) | Zaktualizowano (ta sama wartość) | Przyjmij (obie strony są zgodne) |
| Zaktualizowano (inna wartość) | Zaktualizowano (inna wartość) | **Konflikt** |
| Bez zmian | Usunięto | Przyjmij usunięcie |
| Usunięto | Bez zmian | Przyjmij usunięcie |
| Usunięto | Usunięto | Przyjmij usunięcie (obie strony są zgodne) |
| Zaktualizowano | Usunięto | **Konflikt** |
| Usunięto | Zaktualizowano | **Konflikt** |
| Wstawiono (tylko po naszej stronie) | -- | Przyjmij wstawienie |
| -- | Wstawiono (tylko po ich stronie) | Przyjmij wstawienie |
| Wstawiono (ten sam PK) | Wstawiono (ten sam PK) | **Konflikt** |

Gdy obie strony aktualizują ten sam wiersz, merql nie zgłasza od razu konfliktu. Przechodzi do scalania na poziomie kolumn i porównuje każdą kolumnę osobno. Szczegóły znajdziesz w sekcji [Scalanie na poziomie kolumn](https://merql.inline0.dev/pl/docs/merge/column-level).

## Korzystanie z fasady

Fasada `Merql` działa na nazwanych migawkach zapisanych na dysku. Przed utworzeniem migawki wywołaj `Merql::init()`, przekazując bazę danych zawierającą dany stan:

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

Nie twórz po kolei wszystkich trzech migawek z jednej bazy danych. Migawka ich zmian zawierałaby wtedy również nasze zmiany, więc każdy wiersz dodany przez nas pojawiłby się jako dodany po obu stronach i zostałby zgłoszony jako `insert_insert` konflikt.

`Merql::merge()`, `Merql::patch()` i `Merql::plan()` tworzą `ThreeWayMerge` bez `CellMergeConfig`, więc scalają wyłącznie na poziomie kolumn. Aby scalać na poziomie komórek, wczytaj migawki i użyj bezpośrednio klasy, jak poniżej.

## Bezpośrednie użycie klas

Aby uzyskać większą kontrolę, utwórz instancje komponentów samodzielnie. `Snapshotter::capture()` niczego nie zapisuje, więc przechowuj migawki w pamięci lub zapisz je za pomocą `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'),
);
```

## Skrót patch

Gdy zmiany ma tylko jedna strona (brak równoczesnych modyfikacji), użyj `patch()`. To scalanie dwukierunkowe, w którym nasze zmiany są takie same jak baza, więc konflikty nie są możliwe:

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

Za pośrednictwem fasady:

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

`patch()` jest równoważne `merge($base, $base, $changes)`. Ponieważ nasze zmiany nie odbiegły od bazy, wszystkie zmiany po ich stronie są przyjmowane bez konfliktu.

## Wykrywanie niezgodności schematu

Jeśli między migawkami do tabeli dodano lub z niej usunięto kolumny, scalanie nadal przebiega, ale odnotowuje niezgodności. Zmienione typy kolumn nie są porównywane, a zmieniona nazwa kolumny jest wykazywana jako jedna usunięta i jedna dodana; zobacz sekcję [Walidacja schematu](https://merql.inline0.dev/pl/docs/advanced/schema).

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

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

merql nie przeprowadza automatycznej migracji schematów. Za zmiany schematu odpowiada wywołujący. Wynik scalania je zgłasza, aby można było zdecydować, co dalej.

## Obiekt MergeResult

Obiekt wyniku zawiera wszystko, czego potrzebujesz:

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

Każdy `MergeOperation` ma typ (`insert`, `update` lub `delete`), nazwę tabeli, klucz wiersza, wartości kolumn i źródło (`ours`, `theirs` lub `merged`). `merged` oznacza wiersz zaktualizowany po obu stronach (połączony kolumna po kolumnie) lub usunięty po obu stronach.

## Zastosowanie wyniku scalania

Wynik scalania opisuje, jak przekształcić bazę w stan po scaleniu. Jego operacje obejmują zmiany z obu stron: wstawienie wykonane przez nas i wstawienie wykonane przez nich są uwzględniane jako wstawienia.

- `Merql::apply()` (lub `Applier`) wykonuje każdą operację na połączonej bazie danych. Działa poprawnie na bazie, która nadal zawiera stan bazowy, na przykład na świeżej kopii bazy. W przypadku bazy zawierającej już zmiany jednej ze stron wstawienie istniejącego wiersza kończy się niepowodzeniem i powoduje wycofanie transakcji.
- Aby przenieść nasze zmiany do bazy zawierającej już ich zmiany, na przykład promując zmiany z piaskownicy do środowiska produkcyjnego, zastosuj tylko operacje, których brakuje w tej bazie: te ze źródłem `ours` oraz aktualizacje `merged`. Użyj [planu scalania](https://merql.inline0.dev/pl/docs/merge/plans), aby je wybrać, i [Zastosowania z ochroną](https://merql.inline0.dev/pl/docs/apply/guarded), aby zapisać je z warunkami wstępnymi dotyczącymi bieżących wierszy.
