
# Слияние на уровне столбцов

Слияние на уровне столбцов — главное преимущество 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 может углубить обработку и выполнить [слияние на уровне ячеек](https://merql.inline0.dev/ru/docs/merge/cell-level). Это применяется к столбцам TEXT (построчное сравнение) и JSON (сравнение по ключам) и позволяет разрешать случаи, когда обе стороны изменили разные части значения одного и того же столбца.
