Към съдържанието

Обработване на конфликти

Конфликт възниква, когато двете страни в сливането променят едни и същи данни по несъвместим начин. merql открива четири типа конфликти и предлага автоматични и ръчни стратегии за разрешаването им.

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";
    }
}

Типове конфликти

update_update

И двете страни са променили една и съща колона в един и същи ред с различни стойности. Това е най-често срещаният тип конфликт.

Код
Base:    posts #42  title = "Hello"
Ours:    posts #42  title = "Welcome"
Theirs:  posts #42  title = "Greetings"

Конфликтът съдържа: таблица posts, ключ на ред 42, колона title, наша стойност Welcome, тяхна стойност Greetings, базова стойност Hello.

update_delete

Едната страна е актуализирала ред, а другата го е изтрила. merql не може да определи дали да надделее актуализацията, или изтриването.

Код
Ours:    posts #42  title = "Updated Title"    (updated)
Theirs:  posts #42  (deleted)

Конфликтът съдържа: таблица posts, ключ на ред 42, тип update_delete. Нашата стойност съдържа целия актуализиран ред. Тяхната стойност е null.

delete_update

Обратният случай на update_delete. Те са актуализирали реда, а ние сме го изтрили.

Код
Ours:    posts #42  (deleted)
Theirs:  posts #42  title = "Updated Title"    (updated)

Конфликтът съдържа: тип delete_update. Нашата стойност е null, а тяхната съдържа целия актуализиран ред.

insert_insert

И двете страни са добавили ред със същия първичен ключ. Тъй като и двата реда са нови (не присъстват в базата), merql не може да ги слее.

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

Конфликтът съдържа: тип insert_insert. Нашата стойност съдържа данните за нашия ред, а тяхната — данните за техния ред.

Класът Conflict

Всеки конфликт е обект само за четене със следните средства за достъп:

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)

Методът column() връща името на колона за конфликти update_update и null за структурни конфликти (update_delete, delete_update, insert_insert).

Декодиране на първичния ключ

Ключът на реда е кодиран низ. За да получите масив с именуван първичен ключ, подайте колоните за идентификация:

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

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

Автоматично разрешаване с ConflictPolicy

ConflictResolver прилага правило, за да разреши всички конфликти наведнъж:

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

Стойност Поведение
OursWins Използва нашата стойност за update_update. Запазва актуализацията при update_delete. Запазва изтриването при delete_update. Използва нашия запис при insert_insert.
TheirsWins Използва тяхната стойност за update_update. Изтрива при update_delete. Запазва актуализацията при delete_update. Използва техния запис при insert_insert.
Manual Връща резултата без промяна. Всички конфликти остават неразрешени.

Как работи разрешаването

При конфликти update_update инструментът за разрешаване променя съществуващата операция за сливане на реда, като заменя конфликтната стойност на колоната със стойността от спечелилата страна.

При структурни конфликти (update_delete, delete_update, insert_insert) инструментът за разрешаване добавя нова операция към резултата:

  • update_delete с OursWins добавя операция UPDATE.
  • update_delete с TheirsWins добавя операция DELETE.
  • delete_update с OursWins добавя операция DELETE.
  • delete_update с TheirsWins добавя операция UPDATE.
  • insert_insert използва стойностите на реда от спечелилата страна.

Решеният MergeResult има празен списък с конфликти и може да бъде приложен директно.

Ръчно разрешаване

За по-прецизен контрол проверете всеки конфликт и създайте собствено решение:

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";
    }
}

Прилагане при неразрешени конфликти

Applier отказва да приложи резултат от сливане с неразрешени конфликти. Хвърля ConflictException:

PHP
use Merql\Exceptions\ConflictException;

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

Винаги разрешавайте конфликтите преди прилагане — с ConflictResolver::resolve() или като създадете ръчно изчистен MergeResult.