
# Saraketason yhdistäminen

Saraketason yhdistäminen on merqlin tärkein etu verrattuna yksinkertaiseen rivitason vertailuun. Kun molemmat puolet muuttavat samaa riviä, merql vertaa kutakin saraketta erikseen eikä merkitse koko riviä ristiriidaksi.

## Rivitason yhdistämisen ongelma

Tarkastellaan `posts`-taulua, jossa kaksi henkilöä muokkaa samaa riviä:

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

Rivitason yhdistäminen havaitsee, että molemmat puolet muuttivat riviä, ja ilmoittaa ristiriidan. Muutokset eivät kuitenkaan mene päällekkäin. Ours muokkasi kohdetta `content`, theirs kohteita `title` ja `status`.

## Näin saraketason yhdistäminen ratkaisee ongelman

merql soveltaa samoja kolmen osapuolen yhdistämissääntöjä kuhunkin sarakkeeseen erikseen:

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

Tulos: `{ title: "New Title", content: "Body v2", status: "publish" }`. Yhdistäminen onnistui ilman ristiriitaa.

## Näin se toimii

Kun `ThreeWayMerge` havaitsee, että molemmat puolet päivittivät saman rivin, se siirtää tehtävän kohteelle `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)
```

Kuhunkin sarakkeeseen sovelletaan samoja sääntöjä:

| Lähtötila | Ours | Theirs | Tulos |
|---|---|---|---|
| A | A | A | Säilytä lähtötila (ei muutosta) |
| A | A | B | Hyväksy theirs |
| A | B | A | Hyväksy ours |
| A | B | B | Hyväksy (molemmat samaa mieltä) |
| A | B | C | **Ristiriita** (eri arvot) |

## Milloin ristiriitoja silti syntyy

Saraketason ristiriita syntyy, kun molemmat puolet muuttavat saman sarakkeen eri arvoiksi:

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

Molemmat muuttivat sarakkeen `title` eri arvoksi. Kyseessä on todellinen ristiriita, joka vaatii ratkaisun.

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

## Osittaiset ristiriidat

Yhdellä rivillä voi olla sekä puhtaita yhdistämisiä että ristiriitoja. Jos yksi sarake yhdistyy puhtaasti mutta toisessa on ristiriita, rivin `MergeOperation` sisältää puhtaasti yhdistettyjen sarakkeiden arvot ja ristiriitaisten sarakkeiden arvona meidän arvomme (oletuksena). Ristiriita ilmoitetaan silti erikseen.

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

Tässä `title` aiheuttaa ristiriidan (molemmat muuttivat sitä eri tavalla), `content` yhdistyy puhtaasti (vain meidän puolemme muuttui) ja `status` yhdistyy puhtaasti (vain heidän puolensa muuttui). Toiminto sisältää:

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

## Arvojen vertailu

Sarakkeiden arvoja verrataan merkkijonoina tyyppimuunnoksen jälkeen. `NULL` on eri arvo:

- `NULL`–`NULL` = ei muutosta.
- `NULL`–`"hello"` = päivitys.
- `"hello"`–`NULL` = päivitys.
- `"1"`–`1` = ei muutosta (molemmat muunnetaan merkkijonoksi `"1"`).

Tämä vastaa MerQL-yhteyssovittimen noutosemantiikkaa: kaikki tietokanta-arvot palautetaan merkkijonoina (tai NULL-arvona).

## Solutason yhdistäminen

Jos saraketason yhdistäminen tuottaa silti ristiriidan (molemmat puolet muuttivat saman sarakkeen eri arvoon), merql voi mennä vielä yhden tason syvemmälle [solutason yhdistämisellä](https://merql.inline0.dev/fi/docs/merge/cell-level). Tätä sovelletaan TEXT-sarakkeisiin (rivikohtainen diff) ja JSON-sarakkeisiin (avainkohtainen diff), jolloin ratkaistaan tapaukset, joissa molemmat puolet muokkasivat saman sarakearvon eri osia.
