
# Злиття на рівні стовпців

Головна перевага 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 застосовує однакові правила тристороннього злиття до кожного стовпця окремо:

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

Для кожного стовпця застосовуються ті самі правила:

| Base | Ours | Theirs | Результат |
|---|---|---|---|
| A | A | A | Залишити base (без змін) |
| 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/uk/docs/merge/cell-level). Це застосовується до стовпців TEXT (порівняння рядок за рядком) і JSON (порівняння ключ за ключем), усуваючи випадки, коли обидві сторони змінили різні частини значення того самого стовпця.
