
# Aloitus

Tässä oppaassa käydään läpi asennus, tietokantaan yhdistäminen, tilannevedosten tallentaminen, erojen laskeminen ja kolmen osapuolen yhdistäminen.

## Asennus

```bash
composer require merql/merql
```

merql vaatii PHP 8.2:n tai uudemman sekä `ext-mbstring`- ja `ext-zlib`-laajennukset (molemmat `pitmaster/pitmaster`-riippuvuuden vaatimia). Tarvitset myös käyttämäsi sovittimen laajennuksen: PDO-sovitinta varten `ext-pdo` sekä `ext-pdo_sqlite` tai `ext-pdo_mysql`, mysqli-sovitinta varten `ext-mysqli`. Composer ainoastaan ehdottaa näitä, joten tarkista, että tarvitsemasi laajennus on asennettu.

Paketti asentaa CLI-ohjelman nimellä `vendor/bin/merql`. Käytä merql-versiota 0.4.1 tai uudempaa CLI-ohjelmaa varten; aiemmissa versioissa suoritettava tiedosto ei löytänyt automaattisesti ladattavaa tiedostoa, kun merql oli asennettu riippuvuutena.

## Tietokantaan yhdistäminen

merql toimii pienen yhteyssovittimen rajapinnan kautta. Mukana on sisäänrakennetut sovittimet PDO:lle ja mysqlille sekä murreajurit kohteille MySQL ja SQLite.

```php
use Merql\Connection;

// SQLite (in-memory or file-based, through PDO).
$connection = Connection::sqlite(':memory:');
$connection = Connection::sqlite('/path/to/database.sqlite');

// MySQL through PDO.
$connection = Connection::mysql('127.0.0.1', 'my_database', 'root', 'password');

// MySQL through mysqli.
$connection = Connection::mysqli('127.0.0.1', 'my_database', 'root', 'password');

// Any MySQL or SQLite PDO DSN.
$connection = Connection::fromDsn('mysql:host=127.0.0.1;dbname=my_database;charset=utf8mb4', 'root', 'password');
```

`fromDsn()` avaa minkä tahansa PDO:n tukeman DSN:n, mutta merql sisältää murreajurit vain kohteille MySQL ja SQLite. Jos käytät muuta tietokantamoottoria, kuten `pgsql:`, yhteys avautuu, mutta `Merql::init()` heittää poikkeuksen `Unsupported database driver: pgsql`, kunnes rekisteröit sille ajurin. Katso [Tietokanta-ajurit](https://merql.inline0.dev/fi/docs/apply/drivers).

## Tilannevedoksen tallentaminen

Tilannevedos tallentaa jokaisen rivin jokaisesta taulusta (tai taulujen osajoukosta) sormenjälkinä ja tietoina. Sormenjäljet mahdollistavat nopean muutosten havaitsemisen; koko rivin tiedot ovat käytettävissä, kun eroja tarvitaan.

```php
use Merql\Merql;

Merql::init($connection);

// Snapshot all tables.
$snapshot = Merql::snapshot('baseline');

// Snapshot specific tables.
$snapshot = Merql::snapshot('baseline', ['posts', 'comments', 'users']);
```

Tilannevedokset tallennetaan oletusarvoisesti JSON-tiedostoina sijaintiin `.merql/snapshots/`. Voit vaihtaa sijaintia:

```php
use Merql\Snapshot\SnapshotStore;

SnapshotStore::setDirectory('/var/data/merql-snapshots');
```

## Erojen laskeminen

Erovertailu vertaa kahta tilannevedosta ja palauttaa muutossarjan, joka sisältää lisäykset, päivitykset ja poistot. Molempien tilannevedosten on oltava olemassa; tässä `current` on toinen tilannevedos, joka tallennettiin tietokannan muutosten jälkeen.

```php
$changeset = Merql::diff('baseline', 'current');

echo "Inserts: " . count($changeset->inserts()) . "\n";
echo "Updates: " . count($changeset->updates()) . "\n";
echo "Deletes: " . count($changeset->deletes()) . "\n";

// Inspect changes for a single table.
$postChanges = $changeset->forTable('posts');

// Check per-column diffs on updates.
foreach ($changeset->updates() as $update) {
    echo "Row {$update->rowKey} in {$update->table}:\n";
    foreach ($update->columnDiffs as $diff) {
        echo "  {$diff->column}: {$diff->oldValue} -> {$diff->newValue}\n";
    }
}
```

## Kolmen osapuolen yhdistäminen

Keskeinen toiminto. Ottaa kolme nimettyä tilannevedosta (base, ours, theirs) ja yhdistää kaksi muutosjoukkoa.

Base on tila, josta molemmat kopiot lähtivät. Ours ja theirs ovat kaksi kopiota sen jälkeen, kun niitä on muutettu itsenäisesti, joten kummastakin on tallennettava tilannevedos sen sisältävästä tietokannasta. Esimerkiksi, kun testiympäristö kopioitiin tuotantotietokannasta:

```php
use Merql\Connection;
use Merql\Merql;

$live    = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');

Merql::init($live);
Merql::snapshot('base');      // before the sandbox is copied from live

// ... copy live.sqlite to sandbox.sqlite, then both databases change ...

Merql::init($sandbox);
Merql::snapshot('ours');      // the sandbox with our edits
Merql::init($live);
Merql::snapshot('theirs');    // live with its own changes

$result = Merql::merge('base', 'ours', 'theirs');

echo "Operations: {$result->operationCount()}\n";
echo "Conflicts:  {$result->conflictCount()}\n";
echo "Clean:      " . ($result->isClean() ? 'yes' : 'no') . "\n";
```

Älä ota kaikkia kolmea tilannevedosta samasta tietokannasta peräkkäin. Tällöin theirs sisältäisi jo ours-muutokset, ja jokainen ours-puolen lisäämä rivi näkyisi `insert_insert`-ristiriitana.

`Merql::merge()` yhdistää sarakkeiden tasolla. Jos haluat yhdistää myös TEXT- ja JSON-arvojen sisäisiä muutoksia, käytä `ThreeWayMerge`-asetusta ja `CellMergeConfig`-valitsinta; katso [Solutason yhdistäminen](https://merql.inline0.dev/fi/docs/merge/cell-level).

### Ristiriitojen käsittely

Kun molemmat puolet muuttavat saman sarakkeen eri arvoiksi, merql ilmoittaa ristiriidasta. Voit ratkaista ristiriidat automaattisesti tai tarkastella niitä itse.

```php
use Merql\Merge\ConflictPolicy;
use Merql\Merge\ConflictResolver;

if (!$result->isClean()) {
    // Auto-resolve: one side wins.
    $resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

    // Or inspect each conflict.
    foreach ($result->conflicts() as $conflict) {
        echo "Conflict on {$conflict->table()} row {$conflict->rowKey()}:\n";
        echo "  Type:   {$conflict->type()}\n";
        echo "  Column: {$conflict->column()}\n";
        echo "  Ours:   " . var_export($conflict->oursValue(), true) . "\n";
        echo "  Theirs: " . var_export($conflict->theirsValue(), true) . "\n";
    }
}
```

## Yhdistämisen käyttäminen

Kun yhdistämistulos on puhdas (ristiriitoja ei ole ratkaisematta), käytä sitä tietokantaan, joka välitettiin komennolle `Merql::init()`.

Yhdistämistuloksen toiminnot muuttavat basen yhdistetyksi tilaksi ja sisältävät molempien puolten muutokset. `Merql::apply()` suorittaa ne kaikki, joten tulos soveltuu sellaisenaan tietokantaan, jossa on yhä base-tila. Jos sitä käytetään tietokantaan, joka sisältää jo toisen puolen muutokset, kyseisen puolen lisäämä rivi on jo olemassa, lisäys epäonnistuu ja koko yhdistäminen perutaan. Jos haluat siirtää toisen puolen muutokset tietokantaan, jossa on jo toisen puolen muutokset (esimerkiksi testiympäristön muutokset tuotantoon), käytä vain puuttuvia toimintoja komennolla [Suojattu käyttö](https://merql.inline0.dev/fi/docs/apply/guarded).

```php
$applied = Merql::apply($result);

echo "Rows affected: {$applied->rowsAffected()}\n";

if ($applied->hasErrors()) {
    foreach ($applied->errors() as $error) {
        echo "Error: {$error}\n";
    }
}
```

Soveltaja käärii kaikki toiminnot transaktioon. Jos jokin lause epäonnistuu, koko yhdistäminen perutaan ja virhe palautetaan kohteessa `errors()`. Tällöin `rowsAffected()` sisältää silti ennen virhettä suoritettujen lauseiden määrän, vaikka ne peruttiin ([#4](https://github.com/inline0/merql/issues/4)); tarkista ensin `hasErrors()`.

## Koesuoritus

Esikatsele SQL, jonka yhdistäminen tuottaisi, suorittamatta sitä.

```php
use Merql\Apply\DryRun;

$statements = DryRun::generate($result);

foreach ($statements as $sql) {
    echo "{$sql};\n";
}
```

## Kahden osapuolen korjaus

Jos käytettävissäsi on vain base ja joukko muutoksia (ei rinnakkaisia "ours"-muutoksia), käytä `patch()`. Tämä vastaa yhdistämistä, jossa ours on muuttumaton baseen nähden.

```php
$result = Merql::patch('base', 'changes');
// Always clean: no concurrent edits means no conflicts.
Merql::apply($result);
```

## CLI-ohjelman käyttö

Kaikki nämä toiminnot ovat käytettävissä myös komentoriviltä. Katso [CLI-ohjelman viite](https://merql.inline0.dev/fi/docs/cli), josta löydät kaikki tiedot.

```bash
export MERQL_DB_DSN="sqlite:mydb.sqlite"

vendor/bin/merql snapshot base
# ... change the database ...
vendor/bin/merql snapshot current
vendor/bin/merql diff base current
```

Tee kolmen osapuolen yhdistämistä varten kukin tilannevedos sen tilan sisältävästä tietokannasta ja yhdistä sitten:

```bash
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base
MERQL_DB_DSN="sqlite:/data/sandbox.sqlite" vendor/bin/merql snapshot ours
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot theirs
vendor/bin/merql merge base ours theirs --dry-run
```

## Taulujen ja sarakkeiden suodattaminen

Tilannevedoksista voi jättää pois tauluja (kuten välimuisti- tai istuntotaulut) ja sivuuttaa muuttuvia sarakkeita (kuten `updated_at`-aikaleimoja).

```php
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

$snapshot = $snapshotter->capture('baseline', filters: [
    TableFilter::exclude(['sessions', 'cache_*']),
    ColumnFilter::ignore(['updated_at', 'modified_date']),
]);
```

## Seuraavat vaiheet

- [CLI-ohjelman viite](https://merql.inline0.dev/fi/docs/cli) sisältää komentorivikäyttöliittymän kaikki tiedot.
- [PHP API-viite](https://merql.inline0.dev/fi/docs/api) sisältää kaikkien luokkien ja metodien yksityiskohtaiset ohjeet.
