
# Samenvoegen op kolomniveau

Samenvoegen op kolomniveau is het belangrijkste voordeel van merql ten opzichte van een naïeve vergelijking op rijniveau. Wanneer beide kanten dezelfde rij aanpassen, vergelijkt merql elke kolom afzonderlijk in plaats van de hele rij als conflict aan te merken.

## Het probleem met samenvoegen op rijniveau

Neem een tabel `posts` waarin twee mensen dezelfde rij bewerken:

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

Een samenvoeging op rijniveau ziet dat beide kanten de rij hebben gewijzigd en verklaart een conflict. Maar de wijzigingen overlappen helemaal niet. Wij hebben `content` bewerkt, zij hebben `title` en `status` bewerkt.

## Hoe samenvoegen op kolomniveau dit oplost

merql past dezelfde driewegsamenvoegregels afzonderlijk op elke kolom toe:

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

Resultaat: `{ title: "New Title", content: "Body v2", status: "publish" }`. Schone samenvoeging, geen conflict.

## Werking

Wanneer `ThreeWayMerge` detecteert dat beide kanten dezelfde rij hebben bijgewerkt, delegeert het aan `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)
```

Voor elke kolom gelden dezelfde regels:

| Basis | Wij | Zij | Resultaat |
|---|---|---|---|
| A | A | A | Basis behouden (ongewijzigd) |
| A | A | B | Hun wijziging accepteren |
| A | B | A | Onze wijziging accepteren |
| A | B | B | Accepteren (beide zijn het eens) |
| A | B | C | **Conflict** (verschillende waarden) |

## Wanneer conflicten nog steeds optreden

Een conflict op kolomniveau treedt op wanneer beide kanten dezelfde kolom in verschillende waarden hebben veranderd:

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

Beide kanten hebben `title` gewijzigd in verschillende waarden. Dit is een echt conflict dat moet worden opgelost.

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

## Gedeeltelijke conflicten

Eén rij kan zowel schone samenvoegingen als conflicten bevatten. Als de ene kolom netjes wordt samengevoegd maar een andere conflicteert, bevat de `MergeOperation` voor de rij de samengevoegde waarden voor schone kolommen en standaard onze waarde voor conflicterende kolommen. Het conflict wordt daarnaast afzonderlijk gemeld.

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

Hier conflicteert `title` (beide kanten hebben deze anders gewijzigd), wordt `content` netjes samengevoegd (alleen wij hebben deze gewijzigd) en wordt `status` netjes samengevoegd (alleen zij hebben deze gewijzigd). De bewerking bevat:

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

## Waarden vergelijken

Kolomwaarden worden na conversie vergeleken als strings. `NULL` is een aparte waarde:

- `NULL` naar `NULL` = geen wijziging.
- `NULL` naar `"hello"` = bijgewerkt.
- `"hello"` naar `NULL` = bijgewerkt.
- `"1"` naar `1` = geen wijziging (beide worden geconverteerd naar de string `"1"`).

Dit komt overeen met de ophaalsemantiek van de MerQL-verbindingsadapter: alle databasewaarden komen terug als strings (of NULL).

## Samenvoegen op celniveau

Wanneer samenvoegen op kolomniveau nog steeds een conflict oplevert (beide kanten hebben dezelfde kolom in verschillende waarden gewijzigd), kan merql een niveau dieper gaan met [samenvoegen op celniveau](https://merql.inline0.dev/nl/docs/merge/cell-level). Dit werkt voor TEXT-kolommen (verschillen regel voor regel) en JSON-kolommen (verschillen sleutel voor sleutel) en lost gevallen op waarin beide kanten verschillende delen van dezelfde kolomwaarde hebben bewerkt.
