
# 列単位のマージ

列単位のマージは、単純な行単位の比較に対する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はさらに踏み込んだ[セルレベルのマージ](https://merql.inline0.dev/ja/docs/merge/cell-level)を実行できます。これはTEXT列（行単位の差分）とJSON列（キー単位の差分）に適用され、同じ列の値の異なる部分を両側が編集したケースを解決します。
