跳转到正文

列级合并

与简单的行级比较相比,列级合并是 merql 的主要优势。当双方都修改了同一行时,merql 会分别比较各列,而不会把整行标记为冲突。

行级合并的问题

假设有一个 posts 表,两个人编辑同一行:

代码
Base:    post #42  { title: "Hello",     content: "Body",    status: "draft"   }
Ours:    post #42  { title: "Hello",     content: "Body v2", status: "draft"   }
Theirs:  post #42  { title: "New Title", content: "Body",    status: "publish" }

行级合并会发现双方都修改了该行,于是判定为冲突。但双方的修改完全没有重叠。我方修改了 content,对方修改了 title 和 status。

列级合并如何解决问题

merql 会将相同的三方合并规则分别应用于每一列:

代码
title:   base="Hello", ours="Hello",  theirs="New Title"  -> accept theirs
content: base="Body",  ours="Body v2", theirs="Body"      -> accept ours
status:  base="draft", ours="draft",  theirs="publish"    -> accept theirs

结果:{ title: "New Title", content: "Body v2", status: "publish" }。顺利合并,没有冲突。

工作原理

当 ThreeWayMerge 检测到双方都更新了同一行时,会调用 ColumnMerge::merge():

PHP
use Merql\Merge\ColumnMerge;

$result = ColumnMerge::merge(
    table: 'posts',
    rowKey: '42',
    base: ['title' => 'Hello', 'content' => 'Body', 'status' => 'draft'],
    ours: ['title' => 'Hello', 'content' => 'Body v2', 'status' => 'draft'],
    theirs: ['title' => 'New Title', 'content' => 'Body', 'status' => 'publish'],
);

// $result['values']    -> merged column values
// $result['conflicts'] -> list of Conflict objects (empty if clean)

每一列都应用相同规则:

基线 我方 对方 结果
A A A 保留基线(未变)
A A B 接受对方
A B A 接受我方
A B B 接受(双方一致)
A B C 冲突(值不同)

仍会发生冲突的情况

双方将同一列改成不同值时,就会发生列级冲突:

代码
Base:    post #42  { title: "Hello" }
Ours:    post #42  { title: "Welcome" }
Theirs:  post #42  { title: "Greetings" }

双方都修改了 title,且值不同。这是真正的冲突,需要解决。

PHP
$result = ColumnMerge::merge(
    'posts', '42',
    ['title' => 'Hello'],
    ['title' => 'Welcome'],
    ['title' => 'Greetings'],
);

// $result['conflicts'] contains one Conflict:
//   table: "posts", column: "title"
//   oursValue: "Welcome", theirsValue: "Greetings", baseValue: "Hello"

部分冲突

单行中可以同时存在顺利合并的列和冲突列。如果一列可以顺利合并、另一列发生冲突,那么该行的 MergeOperation 会包含可顺利合并列的合并值,以及发生冲突列的我方值(默认值)。冲突仍会单独报告。

代码
Base:    { title: "Hello", content: "Body",    status: "draft" }
Ours:    { title: "Welcome", content: "Body v2", status: "draft" }
Theirs:  { title: "Greetings", content: "Body",    status: "publish" }

此处 title 发生冲突(双方修改不同),content 顺利合并(仅我方修改),status 顺利合并(仅对方修改)。该操作包含:

PHP
// values: { title: "Welcome", content: "Body v2", status: "publish" }
// One conflict on the "title" column.

值比较

列值转换为字符串后进行比较。NULL 是一个独立的值:

  • NULL 转为 NULL = 无变化。
  • NULL 转为 "hello" = 更新。
  • "hello" 转为 NULL = 更新。
  • "1" 转为 1 = 无变化(两者都转换为字符串 "1")。

这与 MerQL 连接适配器的获取语义一致:所有数据库值都会以字符串(或 NULL)的形式返回。

单元格级合并

如果列级合并仍然产生冲突(双方将同一列改成了不同值),merql 可以进一步执行单元格级合并。它适用于 TEXT 列(按行比较差异)和 JSON 列(按键比较差异),从而解决双方编辑同一列值中不同部分的情况。