
# Aan de slag

Deze handleiding leidt je door de installatie, het verbinden met een database, het vastleggen van snapshots, het berekenen van diffs en het uitvoeren van een drieweg-samenvoeging.

## Installatie

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

merql vereist PHP 8.2+ met `ext-mbstring` en `ext-zlib` (beide vereist door de afhankelijkheid `pitmaster/pitmaster`). Je hebt ook de extensie nodig voor de adapter die je gebruikt: `ext-pdo` met `ext-pdo_sqlite` of `ext-pdo_mysql` voor de PDO-adapter, of `ext-mysqli` voor de mysqli-adapter. Composer doet alleen suggesties, dus controleer of de benodigde extensie is geïnstalleerd.

Het pakket installeert de CLI als `vendor/bin/merql`. Gebruik merql 0.4.1 of later voor de CLI; in eerdere versies kon het binaire bestand de autoloader niet vinden wanneer merql als afhankelijkheid was geïnstalleerd.

## Verbinding maken met een database

merql werkt via een kleine interface voor verbindingsadapters. Er zijn ingebouwde adapters voor PDO en mysqli, met ingebouwde dialectstuurprogramma's voor MySQL en 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()` opent elke DSN die PDO ondersteunt, maar merql bevat alleen dialectstuurprogramma's voor MySQL en SQLite. Bij een andere database-engine, zoals `pgsql:`, wordt de verbinding wel geopend, maar geeft `Merql::init()` een `Unsupported database driver: pgsql`-fout totdat je er een stuurprogramma voor registreert. Zie [Databasestuurprogramma's](https://merql.inline0.dev/nl/docs/apply/drivers).

## Een snapshot vastleggen

Een snapshot legt elke rij van elke tabel (of een deelverzameling van tabellen) vast als fingerprints en gegevens. Fingerprints maken snelle detectie van wijzigingen mogelijk; de volledige rijgegevens zijn beschikbaar wanneer een diff nodig is.

```php
use Merql\Merql;

Merql::init($connection);

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

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

Snapshots worden standaard opgeslagen als JSON-bestanden in `.merql/snapshots/`. Je kunt deze locatie wijzigen:

```php
use Merql\Snapshot\SnapshotStore;

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

## Een diff berekenen

Een diff vergelijkt twee snapshots en retourneert een wijzigingenset met toevoegingen, updates en verwijderingen. Beide snapshots moeten al bestaan; hier is `current` een tweede snapshot die is gemaakt nadat de database was gewijzigd.

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

## Drieweg-samenvoeging

De kernbewerking. Neemt drie benoemde snapshots (base, ours, theirs) en voegt de twee sets wijzigingen samen.

Base is de status waar beide kopieën vanuit zijn gestart. Ours en theirs zijn de twee kopieën nadat ze onafhankelijk zijn gewijzigd; leg elke kopie dus vast vanaf de database waarin deze zich bevindt. Bijvoorbeeld, wanneer een sandbox is gekopieerd vanuit een live database:

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

Maak niet alle drie de snapshots achter elkaar vanuit één database. Theirs bevat dan al de wijzigingen van ours, waardoor elke rij die ours heeft toegevoegd als een `insert_insert`-conflict wordt aangemerkt.

`Merql::merge()` voegt samen op kolomniveau. Gebruik `ThreeWayMerge` met een `CellMergeConfig` om ook binnen TEXT- en JSON-waarden samen te voegen; zie [Samenvoeging op celniveau](https://merql.inline0.dev/nl/docs/merge/cell-level).

### Conflicten afhandelen

Als beide kanten dezelfde kolom wijzigen in verschillende waarden, meldt merql een conflict. Je kunt conflicten automatisch oplossen of ze handmatig bekijken.

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

## De samenvoeging toepassen

Zodra je een schoon samenvoegingsresultaat hebt (zonder onopgeloste conflicten), pas je het toe op de database die aan `Merql::init()` is doorgegeven.

De bewerkingen in een samenvoegingsresultaat zetten base om in de samengevoegde status en bevatten de wijzigingen van beide kanten. `Merql::apply()` voert ze allemaal uit, dus het resultaat wordt probleemloos toegepast op een database die nog de basisstatus bevat. Als je het toepast op een database die de wijzigingen van één kant al bevat, botst elke toevoeging van die kant met een bestaande rij, mislukt de bewerking en wordt de volledige samenvoeging teruggedraaid. Om de wijzigingen van de ene kant door te voeren in een database die de andere kant al bevat (bijvoorbeeld sandbox-bewerkingen naar live), pas je alleen de ontbrekende bewerkingen toe met [Beveiligd toepassen](https://merql.inline0.dev/nl/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";
    }
}
```

De toepasser voert alle bewerkingen uit binnen één transactie. Als een instructie mislukt, wordt de volledige samenvoeging teruggedraaid en verschijnt de fout in `errors()`. In dat geval telt `rowsAffected()` nog steeds de instructies die vóór de fout zijn uitgevoerd, ook al zijn ze teruggedraaid ([#4](https://github.com/inline0/merql/issues/4)); controleer eerst `hasErrors()`.

## Proefuitvoering

Bekijk een voorbeeld van de SQL die een samenvoeging zou genereren, zonder deze uit te voeren.

```php
use Merql\Apply\DryRun;

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

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

## Tweezijdige patch

Als je alleen een base en een set wijzigingen hebt (zonder gelijktijdige wijzigingen van "ours"), gebruik je `patch()`. Dit komt overeen met een samenvoeging waarin ours ongewijzigd is ten opzichte van base.

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

## De CLI gebruiken

Al deze bewerkingen zijn ook beschikbaar via de opdrachtregel. Raadpleeg de [CLI-referentie](https://merql.inline0.dev/nl/docs/cli) voor alle details.

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

Maak voor een drieweg-samenvoeging elke snapshot vanuit de database die de betreffende status bevat en voeg ze daarna samen:

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

## Tabellen en kolommen filteren

Snapshots kunnen tabellen uitsluiten (zoals cache- of sessietabellen) en veranderlijke kolommen negeren (zoals `updated_at`-tijdstempels).

```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']),
]);
```

## Volgende stappen

- [CLI-referentie](https://merql.inline0.dev/nl/docs/cli) voor de volledige opdrachtregelinterface.
- [PHP API-referentie](https://merql.inline0.dev/nl/docs/api) voor gedetailleerde documentatie van elke klasse en methode.
