
# 열 단위 병합

열 단위 병합은 단순한 행 단위 비교에 비해 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" }
```

행 단위 병합은 양쪽에서 행을 변경했다는 이유로 충돌을 선언합니다. 하지만 변경 사항은 전혀 겹치지 않습니다. ours는 `content`을 편집했고, theirs는 `title`과 `status`를 편집했습니다.

## 열 단위 병합으로 해결하는 방법

merql은 각 열에 동일한 3방향 병합 규칙을 개별 적용합니다.

```
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"
```

## 부분 충돌

한 행에서 충돌 없는 병합과 충돌이 함께 발생할 수 있습니다. 한 열은 충돌 없이 병합되지만 다른 열은 충돌하면 행의 `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/ko/docs/merge/cell-level)으로 한 단계 더 세밀하게 처리할 수 있습니다. TEXT 열(줄 단위 diff)과 JSON 열(키 단위 diff)에 적용해 양쪽에서 같은 열 값의 서로 다른 부분을 편집한 경우를 해결합니다.
