
# Referentie voor PHP API

Volledige referentie voor alle openbare klassen en methoden in merql.

## Merql (facade)

`Merql\Merql` is het statische toegangspunt voor veelgebruikte bewerkingen. Het vormt een laag om de onderliggende klassen en beheert het opslaan van snapshots automatisch.

```php
use Merql\Connection;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;
use Merql\Merql;

$connection = Connection::sqlite(':memory:');
Merql::init($connection, identityRules: new IdentityRuleSet([
    'options' => IdentityRule::natural(['option_name']),
]));
```

### `init(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null): void`

Initialiseer de facade met een databaseverbinding. Dit moet gebeuren voordat u `snapshot()`, `apply()` of `applyGuarded()` aanroept; roep deze functie opnieuw aan om van database te wisselen, bijvoorbeeld om ours en theirs uit twee verschillende kopieën als snapshot op te slaan. Zonder `$driver` roept de functie `DriverFactory::create()` aan. Deze geeft een fout voor engines anders dan MySQL en SQLite, tenzij er een driver is geregistreerd. Geef een driver mee om automatische detectie te overschrijven, of een set identiteitsregels om de rij-identiteit per tabel tijdens het vastleggen van een snapshot te overschrijven.

### `snapshot(string $name, array $tables = []): Snapshot`

Leg de huidige databasestatus vast en sla deze op schijf op. Geef een array met tabelnamen mee om een snapshot van specifieke tabellen te maken, of laat deze weg voor alle tabellen.

```php
$snapshot = Merql::snapshot('baseline');
$snapshot = Merql::snapshot('baseline', ['posts', 'users']);
```

### `diff(string $base, string $current): Changeset`

Laad twee snapshots op naam en bereken de wijzigingen tussen deze snapshots. `init()` is niet vereist, omdat de functie met opgeslagen snapshots werkt.

```php
$changeset = Merql::diff('before', 'after');
```

### `merge(string $base, string $ours, string $theirs): MergeResult`

Voert een three-way merge uit van drie snapshots op naam. Geeft een resultaat met conflictvrije bewerkingen en eventuele conflicten. De merge wordt op kolomniveau uitgevoerd: de facade bouwt `ThreeWayMerge` zonder `CellMergeConfig`, dus er wordt geen merge op celniveau toegepast. Gebruik hiervoor rechtstreeks [`ThreeWayMerge`](#threewaymerge).

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

### `patch(string $base, string $changes): MergeResult`

Two-way merge: pas wijzigingen toe op de basis. Gelijk aan `merge($base, $base, $changes)`. Levert altijd een conflictvrij resultaat op, omdat er geen gelijktijdige bewerkingen zijn. Alleen op kolomniveau, net als `merge()`.

```php
$result = Merql::patch('base', 'changes');
```

### `apply(MergeResult $result): ApplyResult`

Voer een conflictvrij mergeresultaat uit op de database die aan `init()` is doorgegeven. `init()` is vereist. Geeft `ConflictException` als er onopgeloste conflicten zijn.

Elke bewerking in het resultaat wordt uitgevoerd. Daarom wordt het volledige resultaat van `merge()` alleen correct toegepast op een database die nog de basisstatus bevat. Als een database al een van beide zijden bevat, pas dan een selectie toe van de bewerkingen die daarin ontbreken; zie [Selectief toepassen](https://merql.inline0.dev/nl/docs/apply/guarded).

```php
$applied = Merql::apply($result);
echo $applied->rowsAffected();
```

### `plan(string $id, string $base, string $ours, string $theirs, array $metadata = []): MergePlan`

Bouw een mergeplan dat klaar is voor UI op basis van drie snapshots op naam. Het plan bevat stabiele bewerkings-ID's, wijzigingsgroep-ID's, bewerkingscontext, conflicten, schemamismatches, een samenvatting, metagegevens en een planhash. De merge wordt op kolomniveau uitgevoerd, net als bij `merge()`; geef een resultaat dat op celniveau is samengevoegd door aan `MergePlanBuilder::build()` voor een merge op celniveau.

```php
$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs', [
    'source' => 'review-ui',
]);
```

### `applyGuarded(MergeResult $result, Snapshot $expectedLive): ApplyResult`

Pas een conflictvrij mergeresultaat toe met optimistische voorwaarden voor live rijen. Voor updates en verwijderingen moeten de huidige live rijen overeenkomen met `$expectedLive`; bij invoegingen mag de doelidentiteit nog niet bestaan. `$expectedLive` is een snapshot van de doeldatabase (theirs, wanneer ours wordt gepromoveerd naar de database waarvan theirs afkomstig is). Elke instructie die nul rijen beïnvloedt, maakt de transactie ongedaan.

```php
use Merql\Snapshot\SnapshotStore;

$applied = Merql::applyGuarded($selectedResult, SnapshotStore::load('theirs'));
```

### `reset(): void`

Wis de interne verbinding en de Snapshotter-instanties. Dit wordt voornamelijk gebruikt in tests om de status tussen uitvoeringen opnieuw in te stellen.

---

## Verbinding

`Merql\Connection` bouwt verbindingsadapters met verstandige standaardinstellingen. PDO-verbindingen gebruiken uitzonderingen bij fouten, een associatieve ophaalmodus, geen geëmuleerde voorbereide instructies en opgehaalde waarden als tekenreeksen. De mysqli-adapter normaliseert opgehaalde waarden naar dezelfde vorm van tekenreeks of null.

### `mysql(string $host, string $database, string $username, string $password = '', int $port = 3306, string $charset = 'utf8mb4'): PdoDatabaseConnection`

```php
use Merql\Connection;

$connection = Connection::mysql('127.0.0.1', 'my_app', 'root', 'secret');
```

### `mysqli(string $host, string $database, string $username, string $password = '', int $port = 3306, string $charset = 'utf8mb4'): MysqliDatabaseConnection`

```php
$connection = Connection::mysqli('127.0.0.1', 'my_app', 'root', 'secret');
```

### `sqlite(string $path = ':memory:'): PdoDatabaseConnection`

Maakt een verbinding met SQLite. Schakelt automatisch foreign keys in via `PRAGMA foreign_keys = ON`.

```php
$connection = Connection::sqlite('/var/data/app.sqlite');
$connection = Connection::sqlite(':memory:');
```

### `fromDsn(string $dsn, string $username = '', string $password = ''): PdoDatabaseConnection`

Maak verbinding met een PDO DSN-tekenreeks. merql bevat alleen drivers voor `mysql` en `sqlite`; andere DSN's openen wel een verbinding, maar `Merql::init()`, `Snapshotter` en de appliers geven `Unsupported database driver`, tenzij er een driver is geregistreerd met `DriverFactory::register()`.

```php
$connection = Connection::fromDsn('mysql:host=localhost;dbname=app', 'root', 'pass');
```

### `fromPdo(PDO $pdo): PdoDatabaseConnection`

Wikkel een bestaande PDO-instantie in.

### `fromMysqli(mysqli $mysqli, string $charset = 'utf8mb4'): MysqliDatabaseConnection`

Wikkel een bestaande mysqli-instantie in.

### Adaptercontract

Alle adapters implementeren `Merql\Database\DatabaseConnection`:

```php
interface DatabaseConnection
{
    public function driverName(): string;
    public function execute(string $sql, array $params = []): int;
    public function query(string $sql, array $params = []): array;
    public function scalar(string $sql, array $params = []): ?string;
    public function beginTransaction(): void;
    public function commit(): void;
    public function rollBack(): void;
    public function lastInsertId(): string;
}
```

`execute()`, `query()` en `scalar()` accepteren positionele `list<scalar|null>`-parameters voor `?`-placeholders. `query()` geeft `list<array<string, scalar|null>>` terug; waarden worden in PDO- en mysqli-adapters genormaliseerd naar dezelfde vorm van tekenreeks of null.

---

## Snapshotter

`Merql\Snapshot\Snapshotter` legt de databasestatus vast. In tegenstelling tot de facade geeft deze u volledige controle over filters en slaat hij niets automatisch op.

### `__construct(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null)`

Als de driver niet is opgegeven, wordt deze automatisch gedetecteerd op basis van de verbindingsidentiteit. Geef een `IdentityRuleSet` mee als de rij-identiteit per tabel de schemastandaarden moet overschrijven.

### `capture(string $name, array $tables = [], array $filters = []): Snapshot`

Leg de huidige status vast. Geef tabelnamen mee om het bereik te beperken. Geef filterobjecten mee om tabellen, kolommen of rijen uit te sluiten.

```php
use Merql\Snapshot\Snapshotter;
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Filter\RowFilter;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;

$snapshotter = new Snapshotter(
    $connection,
    identityRules: new IdentityRuleSet([
        'wp_options' => IdentityRule::natural(['option_name']),
    ]),
);

// All tables.
$snapshot = $snapshotter->capture('full');

// Specific tables with filters.
$snapshot = $snapshotter->capture('filtered', ['posts', 'users'], [
    TableFilter::exclude(['cache_*', 'sessions']),
    ColumnFilter::ignore(['updated_at', 'created_at']),
    RowFilter::create(fn(string $table, array $row) => $row['status'] !== 'trash'),
]);
```

### `captureAliased(string $name, array $physicalToCanonicalTableMap, array $filters = []): Snapshot`

Leg fysieke tabellen vast onder canonieke tabelnamen. Gebruik dit wanneer dezelfde logische tabel in verschillende omgevingen andere fysieke namen heeft.

```php
$snapshot = $snapshotter->captureAliased('sandbox', [
    'wp_onumia_abcd_posts' => 'wp_posts',
    'wp_onumia_abcd_postmeta' => 'wp_postmeta',
]);
```

De resulterende snapshot slaat canonieke tabelnamen op en behoudt tegelijk het fysieke tabelschema, de geconfigureerde identiteitskolommen, rijsleutels en filters.

### `fromData(string $name, array $tableData): Snapshot` (statisch)

Bouw een snapshot op basis van ruwe gegevens zonder databaseverbinding. Handig voor tests of bij het importeren van gegevens uit een andere bron.

```php
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\TableSnapshotData;
use Merql\Schema\TableSchema;

$schema = new TableSchema('posts', ['id' => 'int', 'title' => 'varchar'], ['id']);

$snapshot = Snapshotter::fromData('test', [
    'posts' => new TableSnapshotData($schema, [
        ['id' => '1', 'title' => 'Hello'],
        ['id' => '2', 'title' => 'World'],
    ], ['id']),
]);
```

---

## Snapshot

`Merql\Snapshot\Snapshot` is een alleen-lezen waardeobject dat een volledige databasestatus vertegenwoordigt.

```php
$snapshot->name;                    // string: snapshot identifier
$snapshot->tables;                  // array<string, TableSnapshot>
$snapshot->tableNames();            // list<string>
$snapshot->hasTable('posts');       // bool
$snapshot->getTable('posts');       // ?TableSnapshot
```

---

## TableSnapshot

`Merql\Snapshot\TableSnapshot` bevat de rijen, fingerprints en het schema van één tabel.

```php
$table = $snapshot->getTable('posts');

$table->schema;                    // TableSchema
$table->identityColumns;           // list<string>: columns used for row identity
$table->fingerprints;              // array<string, string>: row key to hash
$table->rows;                      // array<string, array<string, mixed>>

$table->rowKeys();                 // list<string>
$table->rowCount();                // int
$table->hasRow('42');              // bool
$table->getRow('42');              // ?array<string, mixed>
$table->getFingerprint('42');      // ?string
```

---

## TableSnapshotData

`Merql\Snapshot\TableSnapshotData` is invoergegevens voor `Snapshotter::fromData()`.

```php
use Merql\Snapshot\TableSnapshotData;
use Merql\Schema\TableSchema;

$data = new TableSnapshotData(
    schema: new TableSchema('posts', ['id' => 'int', 'title' => 'varchar'], ['id']),
    rows: [
        ['id' => '1', 'title' => 'First Post'],
    ],
    identityColumns: ['id'],
);
```

---

## IdentityRule

`Merql\Identity\IdentityRule` definieert de kolommen waarmee rijsleutels worden opgebouwd.

```php
use Merql\Identity\IdentityRule;

$primary = IdentityRule::primary(['id']);
$natural = IdentityRule::natural(['option_name']);
$composite = IdentityRule::composite(['object_id', 'term_taxonomy_id']);
$content = IdentityRule::content(['name', 'value']);

$key = $natural->key(['option_name' => 'siteurl']);
// "siteurl"
```

### `forSchema(TableSchema $schema): IdentityRule` (statisch)

Bepaal de standaardidentiteitsregel op basis van het schema: primaire sleutel, eerste unieke sleutel, daarna terugval op inhoud.

### `toArray(): array`

Serialiseer de regel als `['type' => string, 'columns' => list<string>]`.

### `fromArray(array $data): IdentityRule` (statisch)

Herstel een regel vanuit geserialiseerde gegevens.

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` slaat identiteitsoverschrijvingen per tabel op.

```php
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;

$rules = new IdentityRuleSet([
    'wp_options' => IdentityRule::natural(['option_name']),
]);

$rule = $rules->ruleFor($schema);
```

### `with(string $table, IdentityRule $rule): IdentityRuleSet`

Geeft een nieuwe regelset terug met een toegevoegde tabeloverschrijving.

### `conflictsFor(string $table, IdentityRule $rule, array $rows): array`

Geef `IdentityConflict`-items terug als meerdere rijen voor een regel dezelfde sleutel opleveren.

### `toArray(): array`

Serialiseer tabelregels als een JSON-veilige array.

### `fromArray(array $data): IdentityRuleSet` (statisch)

Herstel een regelset vanuit geserialiseerde gegevens.

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` slaat snapshots op en laadt ze als JSON-bestanden.

### `setDirectory(string $directory): void`

Stel de opslagmap in. De standaard is `.merql/snapshots`.

### `save(Snapshot $snapshot): void`

Schrijf een snapshot naar schijf.

### `load(string $name): Snapshot`

Laad een snapshot op naam. Geeft `SnapshotException` als deze niet is gevonden.

### `exists(string $name): bool`

Controleer of een snapshotbestand bestaat.

### `delete(string $name): void`

Verwijder een snapshotbestand.

---

## Differ

`Merql\Diff\Differ` vergelijkt twee snapshots en maakt een wijzigingsset.

### `diff(Snapshot $base, Snapshot $current): Changeset`

```php
use Merql\Diff\Differ;

$differ = new Differ();
$changeset = $differ->diff($baseSnapshot, $currentSnapshot);
```

### `valuesEqual(mixed $a, mixed $b): bool` (statisch)

Vergelijk twee waarden door ze naar tekenreeksen om te zetten. NULL wordt als een afzonderlijke waarde behandeld: `NULL === NULL` is waar, `NULL === ''` is onwaar.

---

## Wijzigingsset

`Merql\Diff\Changeset` is een alleen-lezen verzameling bewerkingen op rijniveau.

```php
$changeset->inserts();             // list<RowInsert>
$changeset->updates();             // list<RowUpdate>
$changeset->deletes();             // list<RowDelete>
$changeset->isEmpty();             // bool
$changeset->count();               // int: total operations
$changeset->forTable('posts');     // Changeset: filtered to one table
```

---

## RowInsert, RowUpdate, RowDelete

Alleen-lezen waardeobjecten die afzonderlijke rijwijzigingen vertegenwoordigen.

### RowInsert

```php
$insert->table;                    // string
$insert->rowKey;                   // string
$insert->values;                   // array<string, mixed>
```

### RowUpdate

```php
$update->table;                    // string
$update->rowKey;                   // string
$update->columnDiffs;              // list<ColumnDiff>
$update->fullRow;                  // array<string, mixed>: complete current row
$update->columnDiffMap();          // array<string, ColumnDiff>: keyed by column name
```

### RowDelete

```php
$delete->table;                    // string
$delete->rowKey;                   // string
$delete->oldValues;                // array<string, mixed>: values before deletion
```

---

## ColumnDiff

`Merql\Diff\ColumnDiff` vertegenwoordigt één kolomwijziging binnen een rij-update.

```php
$diff->column;                     // string
$diff->oldValue;                   // mixed
$diff->newValue;                   // mixed
```

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` is het kernalgoritme voor samenvoegen.

### `__construct(?CellMergeConfig $cellMergeConfig = null)`

Geef een `CellMergeConfig` mee om samenvoegen op celniveau in te schakelen voor TEXT- en JSON-kolommen.

```php
use Merql\Merge\ThreeWayMerge;
use Merql\CellMerge\CellMergeConfig;

// Default: column-level merge only.
$merge = new ThreeWayMerge();

// With cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
```

### `merge(Snapshot $base, Snapshot $ours, Snapshot $theirs): MergeResult`

Voer een three-way merge uit. Berekent intern wijzigingssets (van basis naar ours en van basis naar theirs) en voegt deze samen.

```php
$result = $merge->merge($baseSnapshot, $oursSnapshot, $theirsSnapshot);
```

### `patch(Snapshot $base, Snapshot $changes): MergeResult`

Snelkoppeling voor two-way merge. Gelijk aan `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

Geeft `SchemaException[]` terug voor schemawijzigingen die tijdens de laatste merge zijn gevonden (kolommen die tussen snapshots zijn toegevoegd of verwijderd).

---

## MergeResult

`Merql\Merge\MergeResult` is een alleen-lezen waardeobject met de uitkomst van de merge.

```php
$result->isClean();                // bool: no conflicts
$result->operations();             // list<MergeOperation>
$result->conflicts();              // list<Conflict>
$result->operationCount();         // int
$result->conflictCount();          // int
$result->baseSnapshot();           // ?Snapshot
$result->schemaMismatches();       // list<SchemaException>
$result->hasSchemaMismatches();    // bool
```

---

## MergePlan

`Merql\Plan\MergePlan` is een serialiseerbaar reviewmodel voor een merge.

```php
$plan->id;                        // string
$plan->baseSnapshot;              // string
$plan->oursSnapshot;              // string
$plan->theirsSnapshot;            // string
$plan->summary;                   // MergePlanSummary
$plan->changeGroups;              // list<MergePlanChangeGroup>
$plan->operations;                // list<MergePlanOperation>
$plan->conflicts;                 // list<MergePlanConflict>
$plan->schemaMismatches;          // list<string>
$plan->hash;                      // string
$plan->metadata;                  // array<string, mixed>
```

Gebruik `MergePlanSerializer::toJson()` en `::fromJson()` voor stabiele JSON-roundtrips.

## Selecties

`ChangeGroupSelection` slaat gefaseerde wijzigingsgroep-ID's op. Zet deze om in een `OperationSelection` bij het bouwen van geselecteerde mergeresultaten of terugdraaiplannen.

```php
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;

$selection = ChangeGroupSelection::fromIds([$groupId]);
$operationSelection = $selection->toOperationSelection($plan);

$selected = (new SelectedMergeResultFactory())
    ->fromOperationSelection($plan, $operationSelection);
```

## RollbackPlan

`Merql\Rollback\RollbackPlan` slaat inverse bewerkingen en verwachtingen voor afwijkingen op voor een geselecteerde toepassing.

```php
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Rollback\RollbackPlanSerializer;

$rollback = (new RollbackPlanBuilder())->build(
    'rollback-1',
    $plan,
    $operationSelection,
    $liveBeforeRows,
);

$json = RollbackPlanSerializer::toJson($rollback);
```

Gebruik `RollbackDrift` voordat u een terugdraaiing toepast en vervolgens `RollbackPlanApplier` om de inverse bewerkingen uit te voeren.

---

## MergeOperation

`Merql\Merge\MergeOperation` is één opgeloste bewerking uit de merge.

```php
$op->type;                         // string: 'insert' | 'update' | 'delete'
$op->table;                        // string
$op->rowKey;                       // string
$op->values;                       // array<string, mixed>
$op->source;                       // string: 'ours' | 'theirs' | 'merged'
```

Typeconstanten:

```php
MergeOperation::TYPE_INSERT;       // 'insert'
MergeOperation::TYPE_UPDATE;       // 'update'
MergeOperation::TYPE_DELETE;       // 'delete'
```

---

## Conflict

`Merql\Merge\Conflict` vertegenwoordigt een onopgelost mergeconflict.

```php
$conflict->table();                // string
$conflict->rowKey();               // string
$conflict->type();                 // string: 'update_update' | 'update_delete' | 'delete_update' | 'insert_insert'
$conflict->column();               // ?string (null for structural conflicts)
$conflict->oursValue();            // mixed
$conflict->theirsValue();          // mixed
$conflict->baseValue();            // mixed
$conflict->primaryKey(['id']);     // array<string, string>: decoded from rowKey
```

### Conflicttypen

| Type | Betekenis |
|---|---|
| `update_update` | Beide zijden hebben dezelfde kolom gewijzigd in verschillende waarden |
| `update_delete` | Ours heeft een rij bijgewerkt die door theirs is verwijderd |
| `delete_update` | Ours heeft een rij verwijderd die door theirs is bijgewerkt |
| `insert_insert` | Beide zijden hebben een rij ingevoegd met dezelfde primaire sleutel |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` past een oplossingsbeleid toe op alle conflicten in een mergeresultaat.

### `resolve(MergeResult $result, ConflictPolicy $policy): MergeResult` (statisch)

Geeft een nieuwe `MergeResult` terug waarin conflicten volgens het beleid zijn opgelost.

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

// Accept all "theirs" values when conflicts arise.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

// Accept all "ours" values.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::OursWins);

// Return unchanged (for manual inspection).
$resolved = ConflictResolver::resolve($result, ConflictPolicy::Manual);
```

---

## ConflictPolicy

`Merql\Merge\ConflictPolicy` is een backed enum met drie gevallen.

```php
use Merql\Merge\ConflictPolicy;

ConflictPolicy::OursWins;          // Resolve all conflicts in favor of "ours"
ConflictPolicy::TheirsWins;        // Resolve all conflicts in favor of "theirs"
ConflictPolicy::Manual;            // Leave conflicts unresolved for manual handling
```

---

## ColumnMerge

`Merql\Merge\ColumnMerge` voert merge-logica per kolom uit voor één rij. Wordt intern gebruikt door `ThreeWayMerge`.

### `merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array` (statisch)

Geeft `['values' => array, 'conflicts' => list<Conflict>]` terug.

```php
use Merql\Merge\ColumnMerge;

$result = ColumnMerge::merge(
    'posts',
    '42',
    ['title' => 'Hello', 'status' => 'draft'],     // base
    ['title' => 'Hello', 'status' => 'published'],  // ours
    ['title' => 'New Title', 'status' => 'draft'],   // theirs
);

// $result['values'] = ['title' => 'New Title', 'status' => 'published']
// $result['conflicts'] = [] (clean merge)
```

---

## Applier

`Merql\Apply\Applier` voert een mergeresultaat uit op een database.

### `__construct(DatabaseConnection $connection, ?Driver $driver = null)`

### `apply(MergeResult $result, ?Snapshot $base = null): ApplyResult`

Voert alle bewerkingen uit in één transactie. Maakt de transactie bij elke fout ongedaan en rapporteert de fout in `ApplyResult::errors()` in plaats van deze op te werpen. Geeft `ConflictException` als het resultaat onopgeloste conflicten bevat.

```php
use Merql\Apply\Applier;

$applier = new Applier($connection);
$applied = $applier->apply($result);

echo "Rows affected: {$applied->rowsAffected()}\n";
if ($applied->hasErrors()) {
    print_r($applied->errors());
}
```

De volgorde op basis van foreign keys wordt automatisch afgehandeld: invoegingen worden gesorteerd op afhankelijkheden en verwijderingen in omgekeerde volgorde van afhankelijkheden.

---

## ApplyResult

`Merql\Apply\ApplyResult` is een alleen-lezen waardeobject dat door de applier wordt teruggegeven.

```php
$applied->rowsAffected();         // int
$applied->errors();               // list<string>
$applied->hasErrors();            // bool
```

Na een teruggedraaide toepassing telt `rowsAffected()` nog steeds de instructies die vóór de fout zijn uitgevoerd ([#4](https://github.com/inline0/merql/issues/4)). Controleer `hasErrors()` voordat u op het aantal vertrouwt.

---

## DryRun

`Merql\Apply\DryRun` genereert leesbare SQL zonder iets uit te voeren.

### `generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array` (statisch)

Geeft een lijst met tekenreeksen van SQL terug, met ingesloten waarden (alleen voor weergave).

```php
use Merql\Apply\DryRun;

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

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

// Output:
// INSERT INTO `posts` (`id`, `title`) VALUES ('10', 'New Post');
// UPDATE `posts` SET `title` = 'Updated' WHERE `id` = '5';
// DELETE FROM `comments` WHERE `id` = '3';
```

---

## SqlGenerator

`Merql\Apply\SqlGenerator` genereert geparametriseerde SQL-instructies. Wordt intern gebruikt door zowel `Applier` als `DryRun`.

### `generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array` (statisch)

Geeft `list<array{sql: string, params: list<scalar|null>}>` terug.

```php
use Merql\Apply\SqlGenerator;

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

foreach ($statements as $stmt) {
    $connection->execute($stmt['sql'], $stmt['params']);
}
```

---

## CellMergeConfig

`Merql\CellMerge\CellMergeConfig` configureert samenvoegstrategieën op celniveau voor specifieke kolommen of kolomtypen.

### `auto(): self` (statisch)

Handige factory die `TextCellMerger` toewijst aan TEXT/LONGTEXT-kolommen en `JsonCellMerger` aan JSON-kolommen.

```php
use Merql\CellMerge\CellMergeConfig;

$config = CellMergeConfig::auto();
```

### `forColumn(string $column, CellMerger $merger): self`

Wijs een merger toe aan een specifieke kolom. Gebruik `table.column` voor gekwalificeerde namen of alleen `column` voor alle tabellen.

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\TextCellMerger;

$config = (new CellMergeConfig())
    ->forColumn('posts.content', new TextCellMerger())
    ->forColumn('description', new TextCellMerger());
```

### `forType(string $typePattern, CellMerger $merger): self`

Wijs een merger toe aan alle kolommen die overeenkomen met een typepatroon (niet-hoofdlettergevoelige overeenkomst op basis van een substring).

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;

$config = (new CellMergeConfig())
    ->forType('json', new JsonCellMerger());
```

### `getMerger(string $table, string $column, string $columnType = ''): CellMerger`

Zoek de merger op voor een tabel, kolom en type. Prioriteit: gekwalificeerde kolomnaam, niet-gekwalificeerde kolomnaam, typepatroon, standaard (ondoorzichtig).

---

## CellMerger (interface)

`Merql\CellMerge\CellMerger` is de interface voor samenvoegstrategieën op celniveau.

```php
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;

interface CellMerger
{
    public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
}
```

Er zijn drie ingebouwde implementaties beschikbaar.

### TextCellMerger

`Merql\CellMerge\TextCellMerger` voegt tekst over meerdere regels samen met een driewegvergelijking per regel (hetzelfde algoritme dat git gebruikt voor bestandsinhoud). Als beide kanten verschillende regels bewerken, verloopt het samenvoegen probleemloos. Als beide kanten dezelfde regel op verschillende manieren bewerken, wordt een conflict gemeld.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
$result = $merger->merge(
    "line 1\nline 2\nline 3",
    "line 1\nline 2 modified\nline 3",
    "line 1\nline 2\nline 3 modified",
);
// $result->clean === true
// $result->value === "line 1\nline 2 modified\nline 3 modified"
```

### JsonCellMerger

`Merql\CellMerge\JsonCellMerger` voegt JSON-waarden samen door de hoofdsleutels afzonderlijk te vergelijken. Elke sleutel wordt samengevoegd met three-way-logica. Geneste objecten worden als ondoorzichtige JSON-tekenreeksen vergeleken (ze worden niet recursief samengevoegd).

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
$result = $merger->merge(
    '{"a": 1, "b": 2}',
    '{"a": 10, "b": 2}',        // changed "a"
    '{"a": 1, "b": 20}',         // changed "b"
);
// $result->clean === true
// $result->value === '{"a":10,"b":20}'
```

### OpaqueCellMerger

`Merql\CellMerge\OpaqueCellMerger` is de standaard. Deze behandelt waarden als ondoorzichtige tekenreeksen en meldt altijd een conflict als ze verschillen.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` is het retourtype van alle celmergers.

```php
$result->clean;                    // bool: true if merge succeeded
$result->value;                    // mixed: merged value (or ours on conflict)
$result->conflicts;                // int: number of conflicts

// Factory methods.
CellMergeResult::resolved($value);           // clean merge
CellMergeResult::conflict($oursValue, $n);   // conflict with count
```

---

## TableSchema

`Merql\Schema\TableSchema` is een alleen-lezen weergave van de tabelstructuur.

```php
use Merql\Schema\TableSchema;

$schema = new TableSchema(
    name: 'posts',
    columns: ['id' => 'int', 'title' => 'varchar(255)', 'content' => 'text'],
    primaryKey: ['id'],
    uniqueKeys: [['slug']],
);

$schema->name;                     // string
$schema->columns;                  // array<string, string>: name to type
$schema->primaryKey;               // list<string>
$schema->uniqueKeys;               // list<list<string>>
$schema->hasPrimaryKey();          // bool
$schema->hasUniqueKeys();          // bool
$schema->columnNames();            // list<string>
```

---

## Driversysteem

merql gebruikt een uitbreidbaar driversysteem om databasespecifieke bewerkingen te abstraheren. Drivers worden automatisch gedetecteerd op basis van de driveridentiteit van de verbinding.

### Driver (interface)

`Merql\Driver\Driver` definieert het contract dat alle databasestuurprogramma's implementeren.

```php
interface Driver
{
    public function quoteIdentifier(string $name): string;
    public function listTables(DatabaseConnection $connection): array;
    public function readSchema(DatabaseConnection $connection, string $table): TableSchema;
    public function readForeignKeys(DatabaseConnection $connection): array;
    public function selectAll(string $table): string;
}
```

Ingebouwde implementaties: `MysqlDriver` en `SqliteDriver`.

### DriverFactory

`Merql\Driver\DriverFactory` detecteert de driver automatisch op basis van een verbinding.

```php
use Merql\Driver\DriverFactory;

$driver = DriverFactory::create($connection);
```

Registreer een aangepaste driver voor andere databases:

```php
DriverFactory::register('pgsql', MyPostgresDriver::class);
```

---

## Filters

### TableFilter

`Merql\Filter\TableFilter` voegt tabellen toe of sluit ze uit met glob-patronen.

```php
use Merql\Filter\TableFilter;

$filter = TableFilter::exclude(['cache_*', 'sessions', 'temp_*']);
$filter = TableFilter::include(['posts', 'users', 'comments']);
```

### ColumnFilter

`Merql\Filter\ColumnFilter` verwijdert specifieke kolommen uit snapshots.

```php
use Merql\Filter\ColumnFilter;

$filter = ColumnFilter::ignore(['updated_at', 'created_at', 'modified_date']);
```

### RowFilter

`Merql\Filter\RowFilter` filtert rijen met een predicaatfunctie.

```php
use Merql\Filter\RowFilter;

$filter = RowFilter::create(function (string $table, array $row): bool {
    // Exclude trashed posts.
    if ($table === 'posts' && ($row['status'] ?? '') === 'trash') {
        return false;
    }
    return true;
});
```

---

## Uitzonderingen

### SnapshotException

`Merql\Exceptions\SnapshotException` wordt opgegooid wanneer een snapshot niet kan worden gevonden of een ongeldige naam heeft.

```php
SnapshotException::notFound('baseline', '/path/to/baseline.json');
```

### MergeException

`Merql\Exceptions\MergeException` wordt opgegooid wanneer voor een merge een vereiste snapshot ontbreekt.

```php
MergeException::snapshotRequired('base');
```

### ConflictException

`Merql\Exceptions\ConflictException` wordt opgegooid wanneer een mergeresultaat met onopgeloste conflicten wordt toegepast.

```php
ConflictException::unresolved(3);
// Message: "Cannot apply merge with 3 unresolved conflict(s)"
```

### SchemaException

`Merql\Exceptions\SchemaException` wordt opgegooid (of verzameld) wanneer schema's tussen snapshots verschillen.

```php
SchemaException::mismatch('posts', 'column "tags" exists in current but not in base');
```

Schemamismatches stoppen de merge niet. Ze worden verzameld en zijn beschikbaar via `$result->schemaMismatches()` ter inspectie.
