跳转到正文

冲突处理

合并双方以不兼容的方式修改了相同数据时,就会发生冲突。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)

对于 update_update 冲突,column() 方法会返回列名;对于结构性冲突(update_delete、delete_update、insert_insert),则返回 null。

解码主键

行键是经过编码的字符串。要获取带名称的主键数组,请传入标识列:

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。