
# Scalanie na poziomie kolumn

Scalanie na poziomie kolumn to główna przewaga merql nad naiwnym porównywaniem całych wierszy. Gdy obie strony modyfikują ten sam wiersz, merql porównuje każdą kolumnę osobno, zamiast oznaczać cały wiersz jako konflikt.

## Problem ze scalaniem na poziomie wierszy

Weźmy tabelę `posts`, w której dwie osoby edytują ten sam wiersz:

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

Scalanie na poziomie wierszy wykrywa, że obie strony zmieniły wiersz, i zgłasza konflikt. Zmiany jednak wcale się nie nakładają. Nasza strona edytowała `content`, a druga strona — `title` i `status`.

## Jak scalanie na poziomie kolumn rozwiązuje ten problem

merql stosuje te same reguły scalania trójstronnego osobno do każdej kolumny:

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

Wynik: `{ title: "New Title", content: "Body v2", status: "publish" }`. Bezkonfliktowe scalanie.

## Jak to działa

Gdy `ThreeWayMerge` wykryje, że obie strony zaktualizowały ten sam wiersz, przekazuje go do `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)
```

Dla każdej kolumny obowiązują te same reguły:

| Baza | Nasza strona | Druga strona | Wynik |
|---|---|---|---|
| A | A | A | Zachowaj bazę (bez zmian) |
| A | A | B | Przyjmij wartość drugiej strony |
| A | B | A | Przyjmij naszą wartość |
| A | B | B | Przyjmij (obie strony zgodne) |
| A | B | C | **Konflikt** (różne wartości) |

## Kiedy nadal występują konflikty

Konflikt na poziomie kolumn występuje, gdy obie strony zmieniły tę samą kolumnę na różne wartości:

```
Base:    post #42  { title: "Hello" }
Ours:    post #42  { title: "Welcome" }
Theirs:  post #42  { title: "Greetings" }
```

Obie strony zmieniły `title` na różne wartości. To rzeczywisty konflikt wymagający rozwiązania.

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

## Częściowe konflikty

Jeden wiersz może zawierać zarówno bezkonfliktowe scalone wartości, jak i konflikty. Jeśli jedna kolumna scala się poprawnie, a inna powoduje konflikt, `MergeOperation` dla tego wiersza zawiera scalone wartości w kolumnach bez konfliktu, a w kolumnach powodujących konflikt — naszą wartość (domyślnie). Konflikt jest nadal zgłaszany osobno.

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

Tutaj kolumna `title` powoduje konflikt (obie strony zmieniły ją inaczej), `content` scala się bez konfliktu (zmieniła ją tylko nasza strona), a `status` scala się bez konfliktu (zmieniła ją tylko druga strona). Operacja zawiera:

```php
// values: { title: "Welcome", content: "Body v2", status: "publish" }
// One conflict on the "title" column.
```

## Porównywanie wartości

Wartości kolumn są porównywane jako ciągi po rzutowaniu. `NULL` to odrębna wartość:

- `NULL` do `NULL` = bez zmian.
- `NULL` do `"hello"` = aktualizacja.
- `"hello"` do `NULL` = aktualizacja.
- `"1"` do `1` = bez zmian (obie wartości są rzutowane na ciąg `"1"`).

Zgadza się to ze sposobem pobierania danych przez adapter połączenia MerQL: wszystkie wartości z bazy danych są zwracane jako ciągi (lub NULL).

## Scalanie na poziomie komórek

Gdy scalanie na poziomie kolumn nadal powoduje konflikt (obie strony zmieniły tę samą kolumnę na różne wartości), merql może zejść poziom głębiej i wykonać [scalanie na poziomie komórek](https://merql.inline0.dev/pl/docs/merge/cell-level). Działa to dla kolumn TEXT (porównywanie wiersz po wierszu) i JSON (porównywanie klucz po kluczu), rozwiązując sytuacje, w których obie strony edytowały różne części wartości tej samej kolumny.
