
# Konflikthåndtering

Der opstår en konflikt, når begge sider af en fletning ændrer de samme data på måder, der ikke kan forenes. merql registrerer fire typer konflikter og tilbyder automatiske og manuelle løsningsmetoder.

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

if (!$result->isClean()) {
    foreach ($result->conflicts() as $conflict) {
        echo "{$conflict->table()}.{$conflict->column()}: "
           . "{$conflict->oursValue()} vs {$conflict->theirsValue()}\n";
    }
}
```

## Konflikttyper

### update_update

Begge sider ændrede den samme kolonne i den samme række til forskellige værdier. Dette er den mest almindelige konflikttype.

```
Base:    posts #42  title = "Hello"
Ours:    posts #42  title = "Welcome"
Theirs:  posts #42  title = "Greetings"
```

Konflikten rapporterer: tabel `posts`, rækkenøgle `42`, kolonne `title`, vores værdi `Welcome`, deres værdi `Greetings`, baseværdi `Hello`.

### update_delete

Den ene side opdaterede en række, mens den anden slettede den. merql kan ikke afgøre, om opdateringen eller sletningen skal gælde.

```
Ours:    posts #42  title = "Updated Title"    (updated)
Theirs:  posts #42  (deleted)
```

Konflikten rapporterer: tabel `posts`, rækkenøgle `42`, type `update_delete`. Vores værdi indeholder hele den opdaterede række. Deres værdi er `null`.

### delete_update

Det omvendte af update_delete. Deres side opdaterede rækken, mens vores side slettede den.

```
Ours:    posts #42  (deleted)
Theirs:  posts #42  title = "Updated Title"    (updated)
```

Konflikten rapporterer: type `delete_update`. Vores værdi er `null`, og deres værdi indeholder hele den opdaterede række.

### insert_insert

Begge sider indsatte en række med den samme primærnøgle. Da begge rækker er nye (de fandtes ikke i base), kan merql ikke flette dem.

```
Base:    (no row #99)
Ours:    posts #99  { title: "My Post" }
Theirs:  posts #99  { title: "Their Post" }
```

Konflikten rapporterer: type `insert_insert`. Vores værdi indeholder vores rækkedata, og deres værdi indeholder deres rækkedata.

## Klassen Conflict

Hver konflikt er et skrivebeskyttet objekt med følgende adgangsmetoder:

```php
$conflict->table();      // string: table name
$conflict->rowKey();     // string: encoded row identity key
$conflict->type();       // string: "update_update"|"update_delete"|"delete_update"|"insert_insert"
$conflict->column();     // ?string: column name (null for structural conflicts)
$conflict->oursValue();  // mixed: our value (null for delete side, full row for structural)
$conflict->theirsValue(); // mixed: their value (null for delete side, full row for structural)
$conflict->baseValue();  // mixed: base value (null for inserts)
```

Metoden `column()` returnerer et kolonnenavn for konflikter af typen `update_update` og `null` for strukturelle konflikter (`update_delete`, `delete_update`, `insert_insert`).

### Afkodning af primærnøglen

Rækkenøglen er en kodet streng. Hvis du vil have et primærnøglearray med navngivne nøgler, skal du sende identitetskolonnerne:

```php
$pk = $conflict->primaryKey(['id']);
// ['id' => '42']

$pk = $conflict->primaryKey(['tenant_id', 'user_id']);
// ['tenant_id' => '1', 'user_id' => '99']
```

## Automatisk løsning med ConflictPolicy

`ConflictResolver` anvender en politik til at løse alle konflikter på én gang:

```php
use Merql\Merge\ConflictPolicy;
use Merql\Merge\ConflictResolver;

// One side wins everywhere.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::OursWins);
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

// Manual: return the result unchanged (inspect conflicts yourself).
$resolved = ConflictResolver::resolve($result, ConflictPolicy::Manual);
```

### ConflictPolicy-enum

| Værdi | Adfærd |
|---|---|
| `OursWins` | Brug vores værdi for update_update. Behold opdateringen ved update_delete. Behold sletningen ved delete_update. Brug vores indsættelse ved insert_insert. |
| `TheirsWins` | Brug deres værdi for update_update. Slet ved update_delete. Behold opdateringen ved delete_update. Brug deres indsættelse ved insert_insert. |
| `Manual` | Returner resultatet uændret. Alle konflikter forbliver uløste. |

### Sådan fungerer løsningen

Ved `update_update`-konflikter ændrer løseren den eksisterende flettehandling for rækken og erstatter den konfliktfyldte kolonneværdi med den vindende sides værdi.

Ved strukturelle konflikter (`update_delete`, `delete_update`, `insert_insert`) føjer løseren en ny handling til resultatet:

- `update_delete` med `OursWins` tilføjer en UPDATE-handling.
- `update_delete` med `TheirsWins` tilføjer en DELETE-handling.
- `delete_update` med `OursWins` tilføjer en DELETE-handling.
- `delete_update` med `TheirsWins` tilføjer en UPDATE-handling.
- `insert_insert` bruger den vindende sides rækkeværdier.

Den løste `MergeResult` har en tom konfliktliste og kan anvendes direkte.

## Manuel løsning

Hvis du ønsker præcis kontrol, kan du undersøge hver konflikt og opbygge din egen løsning:

```php
foreach ($result->conflicts() as $conflict) {
    echo "Table:  {$conflict->table()}\n";
    echo "Row:    {$conflict->rowKey()}\n";
    echo "Type:   {$conflict->type()}\n";

    if ($conflict->column() !== null) {
        echo "Column: {$conflict->column()}\n";
        echo "Base:   " . var_export($conflict->baseValue(), true) . "\n";
        echo "Ours:   " . var_export($conflict->oursValue(), true) . "\n";
        echo "Theirs: " . var_export($conflict->theirsValue(), true) . "\n";
    }
}
```

## Anvendelse med uløste konflikter

`Applier` nægter at anvende et fletteresultat med uløste konflikter. Den udløser en `ConflictException`:

```php
use Merql\Exceptions\ConflictException;

try {
    $applier->apply($result);
} catch (ConflictException $e) {
    echo $e->getMessage();
    // "Cannot apply merge with 3 unresolved conflicts"
}
```

Løs altid konflikter, før du anvender resultatet, enten med `ConflictResolver::resolve()` eller ved manuelt at opbygge en ren `MergeResult`.
