コンテンツへスキップ

列単位のマージ

列単位のマージは、単純な行単位の比較に対するmerqlの主な利点です。両側が同じ行を変更した場合、merqlは行全体を競合として扱うのではなく、各列を個別に比較します。

行単位マージの問題点

2人が同じ行を編集する 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" }

行単位のマージでは、両側で行が変更されたことを検出すると、競合と判断します。しかし、変更はまったく重複していません。oursは content を編集し、theirsは 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)

各列には同じ規則が適用されます:

基準 ours theirs 結果
A A A 基準を維持(変更なし)
A A B theirsを採用
A B A oursを採用
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"

部分的な競合

1つの行に、正常にマージされる列と競合する列の両方が含まれることがあります。ある列は正常にマージできても、別の列で競合する場合、行のMergeOperationには正常にマージできた列のマージ後の値と、競合した列のoursの値(デフォルト)が含まれます。競合は別途報告されます。

コード
Base:    { title: "Hello", content: "Body",    status: "draft" }
Ours:    { title: "Welcome", content: "Body v2", status: "draft" }
Theirs:  { title: "Greetings", content: "Body",    status: "publish" }

ここではtitleが競合し(両側が異なる変更を行い)、contentは正常にマージされ(oursのみ変更)、statusも正常にマージされます(theirsのみ変更)。この操作には次が含まれます:

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列(キー単位の差分)に適用され、同じ列の値の異なる部分を両側が編集したケースを解決します。