
# Konflikthantering

En konflikt uppstår när båda sidor av en sammanslagning ändrar samma data på sätt som inte går ihop. merql upptäcker fyra typer av konflikter och erbjuder automatiska och manuella lösningsstrategier.

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

Båda sidorna ändrade samma kolumn i samma rad till olika värden. Detta är den vanligaste konflikttypen.

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

Konflikten rapporterar: tabell `posts`, radnyckel `42`, kolumn `title`, värde på vår sida `Welcome`, värde på deras sida `Greetings`, basvärde `Hello`.

### update_delete

Den ena sidan uppdaterade en rad, den andra tog bort den. merql kan inte avgöra om uppdateringen eller borttagningen ska gälla.

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

Konflikten rapporterar: tabell `posts`, radnyckel `42`, typ `update_delete`. Värdet på vår sida innehåller den fullständigt uppdaterade raden. Värdet på deras sida är `null`.

### delete_update

Motsatsen till update_delete. De uppdaterade raden, vi tog bort den.

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

Konflikten rapporterar: typ `delete_update`. Värdet på vår sida är `null`, och värdet på deras sida innehåller den fullständigt uppdaterade raden.

### insert_insert

Båda sidorna infogade en rad med samma primärnyckel. Eftersom båda raderna är nya (fanns inte i basen) kan merql inte slå samman dem.

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

Konflikten rapporterar: typ `insert_insert`. Värdet på vår sida innehåller våra raddata, och värdet på deras sida innehåller deras raddata.

## Klassen Conflict

Varje konflikt är ett skrivskyddat objekt med följande åtkomstmetoder:

```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()` returnerar ett kolumnnamn för konflikter av typen `update_update` och `null` för strukturella konflikter (`update_delete`, `delete_update`, `insert_insert`).

### Avkoda primärnyckeln

Radnyckeln är en kodad sträng. Om du vill få en namngiven primärnyckelarray skickar du med identitetskolumnerna:

```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` tillämpar en princip för att lösa alla konflikter på en gång:

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

### Uppräkningen ConflictPolicy

| Värde | Beteende |
|---|---|
| `OursWins` | Använd vårt värde vid update_update. Behåll uppdateringen vid update_delete. Behåll borttagningen vid delete_update. Använd vår infogning vid insert_insert. |
| `TheirsWins` | Använd deras värde vid update_update. Ta bort vid update_delete. Behåll uppdateringen vid delete_update. Använd deras infogning vid insert_insert. |
| `Manual` | Returnera resultatet oförändrat. Alla konflikter förblir olösta. |

### Så fungerar lösningen

Vid konflikter av typen `update_update` uppdaterar lösaren den befintliga sammanslagningsåtgärden för raden och ersätter det motstridiga kolumnvärdet med det vinnande värdet.

Vid strukturella konflikter (`update_delete`, `delete_update`, `insert_insert`) lägger lösaren till en ny åtgärd i resultatet:

- `update_delete` med `OursWins` lägger till en UPDATE-åtgärd.
- `update_delete` med `TheirsWins` lägger till en DELETE-åtgärd.
- `delete_update` med `OursWins` lägger till en DELETE-åtgärd.
- `delete_update` med `TheirsWins` lägger till en UPDATE-åtgärd.
- `insert_insert` använder det vinnande värdet för raden.

Det lösta `MergeResult` har en tom konfliktlista och kan tillämpas direkt.

## Manuell lösning

Om du vill ha detaljerad kontroll granskar du varje konflikt och skapar en 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";
    }
}
```

## Tillämpa med olösta konflikter

`Applier` vägrar att tillämpa ett sammanslagningsresultat med olösta konflikter. Den kastar ett `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 alltid konflikter innan du tillämpar resultatet, antingen med `ConflictResolver::resolve()` eller genom att manuellt skapa ett rent `MergeResult`.
