
# Îmbinare în trei direcții

O îmbinare în trei direcții compară două seturi de modificări cu un strămoș comun și le combină într-un singur rezultat. merql aplică acest algoritm rândurilor din baza de date, în locul liniilor din fișiere.

```php
use Merql\Merge\ThreeWayMerge;

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

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

## Cele trei stări

Fiecare îmbinare necesită exact trei instantanee:

- **Bază** -- starea bazei de date în momentul în care cele două ramuri au divergat.
- **A noastră** -- o ramură după modificările sale, de exemplu o copie sandbox care a fost editată.
- **A lor** -- cealaltă ramură după modificările sale, de exemplu baza de date activă care a continuat să funcționeze.

Stările noastră și a lor trebuie capturate fiecare din baza de date care conține ramura respectivă. Regulile de îmbinare tratează ambele părți în mod egal; denumirile stabilesc `source` fiecărei operații, numele tipurilor de conflict (`update_delete` înseamnă că noi am actualizat, iar ei au șters) și partea favorizată de `ConflictPolicy::OursWins` și `TheirsWins`.

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

Fără o bază comună, nu puteți distinge „adăugat” de „nemodificat”. Comparația în două direcții arată că partea A are o valoare, iar partea B nu, dar nu poate determina dacă A a adăugat-o sau B a șters-o. Baza răspunde la această întrebare.

## Cum se calculează seturile de modificări

`Differ` compară fiecare instantaneu cu baza pentru a produce două seturi de modificări. Fiecare set conține trei tipuri de operații:

| Bază | Curent | Operație |
|---|---|---|
| Rândul există | Rândul există, amprentă identică | Nicio modificare |
| Rândul există | Rândul există, amprentă diferită | **UPDATE** (diferențiere pentru fiecare coloană) |
| Rândul există | Rândul lipsește | **DELETE** |
| Rândul lipsește | Rândul există | **INSERT** |

Amprentele (SHA-256 hash-uri ale conținutului normalizat al rândului) oferă o cale rapidă. Dacă două amprente coincid, rândul nu s-a schimbat și nu este necesară compararea la nivel de coloană.

```php
use Merql\Diff\Differ;

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

## Reguli de îmbinare

Cele două seturi de modificări sunt apoi îmbinate operație cu operație. Acest tabel definește rezultatul pentru fiecare combinație:

| Ale noastre | Ale lor | Rezultat |
|---|---|---|
| Fără modificări | Fără modificări | Păstrează baza |
| Fără modificări | Actualizat | Acceptă varianta lor |
| Actualizat | Fără modificări | Acceptă varianta noastră |
| Actualizat (aceeași valoare) | Actualizat (aceeași valoare) | Acceptă (ambele sunt de acord) |
| Actualizat (valori diferite) | Actualizat (valori diferite) | **Conflict** |
| Fără modificări | Șters | Acceptă ștergerea |
| Șters | Fără modificări | Acceptă ștergerea |
| Șters | Șters | Acceptă ștergerea (ambele sunt de acord) |
| Actualizat | Șters | **Conflict** |
| Șters | Actualizat | **Conflict** |
| Inserat (doar al nostru) | -- | Acceptă inserarea |
| -- | Inserat (doar al lor) | Acceptă inserarea |
| Inserat (același PK) | Inserat (același PK) | **Conflict** |

Când ambele părți actualizează același rând, merql nu declară imediat un conflict. Trecerea se face la îmbinarea la nivel de coloană, comparând fiecare coloană separat. Pentru detalii, consultați [Îmbinare la nivel de coloană](https://merql.inline0.dev/ro/docs/merge/column-level).

## Utilizarea facade-ului

`Merql` facade-ul operează asupra unor instantanee denumite, persistate pe disc. Apelați `Merql::init()` cu baza de date care conține fiecare stare înainte de a crea instantaneul:

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

Nu creați toate cele trei instantanee pe rând dintr-o singură bază de date. Varianta lor ar conține atunci și modificările noastre, astfel că fiecare rând inserat de noi ar apărea ca inserat de ambele părți și ar fi raportat drept `insert_insert` conflict.

`Merql::merge()`, `Merql::patch()` și `Merql::plan()` creează un `ThreeWayMerge` fără `CellMergeConfig`, așa că îmbină doar la nivel de coloană. Pentru îmbinare la nivel de celulă, încărcați instantaneele și folosiți direct clasa, ca mai jos.

## Utilizarea directă a claselor

Pentru mai mult control, instanțiați singuri componentele. `Snapshotter::capture()` nu persistă nimic, așa că păstrați instantaneele în memorie sau salvați-le cu `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'),
);
```

## Scurtătura pentru patch-uri

Când doar o parte are modificări (fără modificări concurente), folosiți `patch()`. Aceasta este o îmbinare bidirecțională în care varianta noastră este identică cu baza, deci conflictele sunt imposibile:

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

Prin facade:

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

`patch()` este echivalent cu `merge($base, $base, $changes)`. Deoarece varianta noastră nu s-a abătut niciodată de la bază, toate modificările din varianta lor sunt acceptate fără conflict.

## Detectarea nepotrivirilor de schemă

Dacă s-au adăugat sau eliminat coloane dintr-un tabel între instantanee, îmbinarea continuă, dar înregistrează nepotrivirile. Tipurile coloanelor modificate nu sunt comparate, iar o coloană redenumită apare ca una eliminată și una adăugată; consultați [Validarea schemei](https://merql.inline0.dev/ro/docs/advanced/schema).

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

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

merql nu migrează automat schemele. Modificările schemei țin de responsabilitatea apelantului. Îmbinarea le raportează, astfel încât să puteți decide cum să procedați.

## MergeResult

Obiectul rezultat conține tot ce vă trebuie:

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

Fiecare `MergeOperation` are un tip (`insert`, `update` sau `delete`), un nume de tabel, o cheie de rând, valorile coloanelor și o sursă (`ours`, `theirs` sau `merged`). `merged` marchează un rând actualizat de ambele părți (combinat coloană cu coloană) sau șters de ambele părți.

## Unde se aplică rezultatul unei îmbinări

Rezultatul unei îmbinări descrie cum se transformă baza în starea îmbinată. Operațiile sale conțin modificările ambelor părți: o inserare făcută de noi și una făcută de ei apar ambele ca inserări.

- `Merql::apply()` (sau `Applier`) execută fiecare operație asupra bazei de date conectate. Se aplică fără probleme unei baze de date care încă păstrează baza, de exemplu unei copii proaspete a stării de bază. Dacă baza de date conține deja modificările uneia dintre părți, inserarea unui rând existent eșuează, iar tranzacția este anulată.
- Pentru a aduce varianta noastră într-o bază de date care conține deja varianta lor, de exemplu pentru a promova modificările din sandbox în mediul live, aplicați doar operațiile care lipsesc din acea bază de date: cele cu sursa `ours` și actualizările `merged`. Folosiți un [plan de îmbinare](https://merql.inline0.dev/ro/docs/merge/plans) pentru a le selecta și [Aplicare protejată](https://merql.inline0.dev/ro/docs/apply/guarded) pentru a le scrie cu precondiții asupra rândurilor live.
