Hoppa till innehåll

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.

Kod
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.

Kod
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.

Kod
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.

Kod
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.