
# PHP API-referencemateriale

Komplet reference til alle offentlige klasser og metoder i merql.

## Merql (facade)

`Merql\Merql` er det statiske indgangspunkt til almindelige handlinger. Det indkapsler klasserne på lavere niveau og håndterer automatisk lagring af snapshots.

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

Initialisér facaden med en databaseforbindelse. Skal kaldes før
`snapshot()`, `apply()` eller `applyGuarded()`; kald den igen for at skifte database,
for eksempel for at tage snapshots af ours og theirs fra to forskellige kopier. Uden en
`$driver` kalder den `DriverFactory::create()`, som kaster en undtagelse for andre databasemotorer
end MySQL og SQLite, medmindre en driver er registreret. Angiv en driver for at tilsidesætte automatisk registrering eller et sæt identitetsregler for at tilsidesætte rækkeidentiteten pr. tabel under optagelse af snapshots.

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

Optag den aktuelle databasetilstand, og gem den på disk. Angiv en liste over tabelnavne for kun at tage snapshots af bestemte tabeller, eller udelad den for at tage snapshots af alle tabeller.

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

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

Indlæs to navngivne snapshots, og beregn ændringssættet mellem dem. Kræver ikke `init()`, da det arbejder ud fra gemte snapshots.

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

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

Trevejssammenfletning af tre navngivne snapshots. Returnerer et resultat med konfliktfrie handlinger og eventuelle konflikter. Sammenfletter på kolonneniveau: facaden opretter `ThreeWayMerge` uden en `CellMergeConfig`, så sammenfletning på celleniveau ikke anvendes. Brug [`ThreeWayMerge`](#threewaymerge) direkte til det.

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

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

Tovejssammenfletning: anvend ændringer på grundlaget. Svarer til `merge($base, $base, $changes)`. Giver altid et konfliktfrit resultat, da der ikke er samtidige redigeringer. Kun på kolonneniveau, ligesom `merge()`.

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

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

Udfør et konfliktfrit sammenfletningsresultat på databasen, der blev sendt til `init()`. Kræver `init()`. Kaster `ConflictException`, hvis der stadig er uløste konflikter.

Hver handling i resultatet udføres, så hele resultatet af `merge()` kan kun anvendes konfliktfrit på en database, der stadig har grundlagstilstanden. Hvis databasen allerede har tilstanden fra den ene side, skal du anvende et udvalg af de handlinger, den mangler; se [Beskyttet anvendelse](https://merql.inline0.dev/da/docs/apply/guarded).

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

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

Byg en sammenfletningsplan klar til UI ud fra tre navngivne snapshots. Planen indeholder stabile
handlings-ID'er, ID'er for ændringsgrupper, handlingskontekst, konflikter, skemaforskelle,
et resumé, metadata og en planhash. Sammenfletningen sker på kolonneniveau som i
`merge()`; send et cellesammenflettet resultat til `MergePlanBuilder::build()` for
sammenfletning på celleniveau.

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

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

Anvend et konfliktfrit sammenfletningsresultat med optimistiske forudsætninger for live-rækker. Opdateringer og
sletninger kræver, at de aktuelle live-rækker svarer til `$expectedLive`; ved indsættelser må
target-identiteten ikke allerede findes. `$expectedLive` er et snapshot af
databasen som mål (theirs, når ours overføres til den database, som theirs
blev taget fra). Hvis en kommando ikke påvirker nogen rækker, rulles transaktionen tilbage.

```php
use Merql\Snapshot\SnapshotStore;

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

### `reset(): void`

Ryd de interne forbindelses- og Snapshotter-instanser. Bruges primært i test til at nulstille tilstanden mellem kørsler.

---

## Forbindelse

`Merql\Connection` opretter forbindelsesadaptere med fornuftige standardindstillinger. PDO-forbindelser bruger undtagelser ved fejl, associativ hentetilstand, ingen emulerede forberedte sætninger og hentede værdier som strenge. mysqli-adapteren normaliserer hentede værdier til samme streng-eller-null-format.

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

Opretter en SQLite-forbindelse. Aktiverer automatisk fremmednøgler 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`

Opret forbindelse med en vilkårlig PDO DSN-streng. merql leveres kun med drivere til `mysql` og `sqlite`; andre DSN'er opretter en forbindelse, men `Merql::init()`, `Snapshotter` og applikatorerne kaster `Unsupported database driver`, medmindre en driver er registreret med `DriverFactory::register()`.

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

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

Ombryd en eksisterende PDO-instans.

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

Ombryd en eksisterende mysqli-instans.

### Adapterkontrakt

Alle adaptere implementerer `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()` og `scalar()` accepterer positionelle `list<scalar|null>`
parametre for pladsholdere af typen `?`. `query()` returnerer
`list<array<string, scalar|null>>`; værdier normaliseres til samme
streng-eller-null-format på tværs af PDO- og mysqli-adaptere.

---

## Snapshotter

`Merql\Snapshot\Snapshotter` optager databasetilstanden. I modsætning til facaden giver den dig fuld kontrol over filtre og gemmer ikke automatisk.

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

Driveren registreres automatisk ud fra forbindelsens identitet, hvis den ikke angives. Angiv et
`IdentityRuleSet`, når tabelspecifik rækkeidentitet skal tilsidesætte skemaets
standardindstillinger.

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

Optag den aktuelle tilstand. Angiv tabelnavne for at begrænse omfanget. Angiv filterobjekter for at udelukke tabeller, kolonner eller rækker.

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

Optag fysiske tabeller under kanoniske tabelnavne. Brug dette, når den samme
logiske tabel har forskellige fysiske navne på tværs af miljøer.

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

Det resulterende snapshot gemmer kanoniske tabelnavne, samtidig med at det bevarer
det fysiske tabelskema, konfigurerede identitetskolonner, rækkenøgler og filtre.

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

Byg et snapshot ud fra rådata uden en databaseforbindelse. Nyttigt til test eller ved import af data fra en anden kilde.

```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` er et skrivebeskyttet værdiobjekt, der repræsenterer en fuldstændig databasetilstand.

```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` indeholder én tabels rækker, fingeraftryk og skema.

```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` er inputdata til `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` definerer de kolonner, der bruges til at opbygge rækkenøgler.

```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` (statisk)

Find standardidentitetsreglen ud fra skemaet: primærnøgle, første unikke nøgle,
derefter fallback til indhold.

### `toArray(): array`

Serialisér reglen som `['type' => string, 'columns' => list<string>]`.

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

Opret en regel ud fra serialiserede data.

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` gemmer tabelspecifikke tilsidesættelser af identitet.

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

Returner et nyt regelsæt med én tilføjet tabeltilsidesættelse.

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

Returner `IdentityConflict`-poster, når flere rækker giver samme nøgle for en
regel.

### `toArray(): array`

Serialisér tabelregler som et JSON-sikkert array.

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

Opret et regelsæt ud fra serialiserede data.

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` gemmer og indlæser snapshots som JSON-filer.

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

Angiv lagermappen. Standard er `.merql/snapshots`.

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

Skriv et snapshot til disk.

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

Indlæs et snapshot efter navn. Kaster `SnapshotException`, hvis det ikke findes.

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

Kontrollér, om en snapshotfil findes.

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

Fjern en snapshotfil.

---

## Differ

`Merql\Diff\Differ` sammenligner to snapshots og opretter et ændringssæt.

### `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` (statisk)

Sammenlign to værdier ved at tvinge dem til strenge. NULL behandles som en særskilt værdi: `NULL === NULL` er sand, `NULL === ''` er falsk.

---

## Ændringssæt

`Merql\Diff\Changeset` er en skrivebeskyttet samling af handlinger på rækkeniveau.

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

Skrivebeskyttede værdiobjekter, der repræsenterer ændringer af enkelte rækker.

### 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` repræsenterer en enkelt kolonneændring i en rækkeopdatering.

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

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` er den centrale sammenfletningsalgoritme.

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

Angiv en `CellMergeConfig` for at aktivere sammenfletning på celleniveau for TEXT- og JSON-kolonner.

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

Udfør en trevejssammenfletning. Beregner ændringssæt internt (fra base til ours og fra base til theirs) og sammenfletter dem.

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

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

Genvej til tovejssammenfletning. Svarer til `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

Returnerer `SchemaException[]` for alle skemaforskelle, der blev registreret under den seneste sammenfletning (kolonner tilføjet eller fjernet mellem snapshots).

---

## MergeResult

`Merql\Merge\MergeResult` er et skrivebeskyttet værdiobjekt, der indeholder resultatet af sammenfletningen.

```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` er en serialiserbar reviewmodel for en sammenfletning.

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

Brug `MergePlanSerializer::toJson()` og `::fromJson()` til stabile JSON-ture.

## Udvalg

`ChangeGroupSelection` gemmer trinvist tilføjede ID'er for ændringsgrupper. Konvertér den til en
`OperationSelection`, når du opbygger udvalgte sammenfletningsresultater eller tilbagerulningsplaner.

```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` gemmer inverse handlinger og forventninger til afvigelser
for en udvalgt anvendelse.

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

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

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

Brug `RollbackDrift` før rollback, og brug derefter `RollbackPlanApplier` til at køre de inverse operationer.

---

## MergeOperation

`Merql\Merge\MergeOperation` er en enkelt afklaret operation fra sammenfletningen.

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

Typekonstanter:

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

---

## Konflikt

`Merql\Merge\Conflict` repræsenterer en uafklaret sammenfletningskonflikt.

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

### Konflikttyper

| Type | Betydning |
|---|---|
| `update_update` | Begge sider ændrede den samme kolonne til forskellige værdier |
| `update_delete` | Vores side opdaterede en række, som deres side slettede |
| `delete_update` | Vores side slettede en række, som deres side opdaterede |
| `insert_insert` | Begge sider indsatte en række med samme primærnøgle |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` anvender en løsningspolitik på alle konflikter i resultatet af en sammenfletning.

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

Returnerer en ny `MergeResult` med konflikter løst i henhold til politikken.

```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` er en backed enum med tre cases.

```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` udfører sammenfletningslogik pr. kolonne for en enkelt række. Bruges internt af `ThreeWayMerge`.

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

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

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

---

## Udfører

`Merql\Apply\Applier` udfører et sammenfletningsresultat på en database.

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

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

Udfører alle operationer i en enkelt transaktion. Ruller tilbage ved enhver fejl og rapporterer den i `ApplyResult::errors()` i stedet for at kaste den. Kaster `ConflictException`, hvis resultatet indeholder uafklarede konflikter.

```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());
}
```

Fremmednøglerækkefølgen håndteres automatisk: Indsættelser sorteres efter afhængigheder, og sletninger sorteres i omvendt afhængighedsrækkefølge.

---

## ApplyResult

`Merql\Apply\ApplyResult` er et skrivebeskyttet værdiobjekt, som returneres af udføreren.

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

Efter en tilbagerullet anvendelse tæller `rowsAffected()` stadig de sætninger, der blev kørt før fejlen ([#4](https://github.com/inline0/merql/issues/4)). Kontrollér `hasErrors()`, før du stoler på tallet.

---

## DryRun

`Merql\Apply\DryRun` genererer menneskeligt læsbare SQL uden at udføre noget.

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

Returnerer en liste med SQL-strenge, hvor værdierne er indsat (kun til visning).

```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` genererer parametriserede SQL-sætninger. Bruges internt af både `Applier` og `DryRun`.

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

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

```php
use Merql\Apply\SqlGenerator;

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

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

---

## CellMergeConfig

`Merql\CellMerge\CellMergeConfig` konfigurerer sammenfletningsstrategier på celleniveau for bestemte kolonner eller kolonnetyper.

### `auto(): self` (statisk)

Praktisk factory, der tildeler `TextCellMerger` til TEXT/LONGTEXT-kolonner og `JsonCellMerger` til JSON-kolonner.

```php
use Merql\CellMerge\CellMergeConfig;

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

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

Tildel en sammenfletter til en bestemt kolonne. Brug `table.column` til kvalificerede navne eller blot `column` til alle tabeller.

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

Tildel en sammenfletter til alle kolonner, der matcher et typemønster (uafhængigt af store og små bogstaver, delstrengsmatch).

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

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

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

Find sammenfletteren for en given tabel, kolonne og type. Prioritet: kvalificeret kolonnenavn, ukvalificeret kolonnenavn, typemønster, standard (opaque).

---

## CellMerger (grænseflade)

`Merql\CellMerge\CellMerger` er grænsefladen for sammenfletningsstrategier på celleniveau.

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

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

Der findes tre indbyggede implementeringer.

### TextCellMerger

`Merql\CellMerge\TextCellMerger` sammenfletter tekst med flere linjer ved hjælp af en trevejssammenligning linje for linje (samme algoritme, som git bruger til filindhold). Når begge sider redigerer forskellige linjer, lykkes sammenfletningen uden konflikter. Når begge sider redigerer den samme linje forskelligt, rapporteres en konflikt.

```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` sammenfletter JSON-værdier ved at sammenligne nøgler på øverste niveau uafhængigt. Hver nøgle sammenflettes med trevejslogik. Indlejrede objekter sammenlignes som opaque JSON-strenge (de sammenflettes ikke rekursivt).

```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` er standarden. Den behandler værdier som opaque strenge og rapporterer altid en konflikt, når de er forskellige.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` er returtypen for alle celle-sammenflettere.

```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` er en skrivebeskyttet repræsentation af tabelstrukturen.

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

---

## Driversystem

merql bruger et system med udskiftelige drivere til at abstrahere databasespecifikke operationer. Drivere registreres automatisk ud fra forbindelsens driveridentitet.

### Driver (grænseflade)

`Merql\Driver\Driver` definerer den kontrakt, som alle databasedrivere implementerer.

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

Indbyggede implementeringer: `MysqlDriver` og `SqliteDriver`.

### DriverFactory

`Merql\Driver\DriverFactory` registrerer automatisk driveren ud fra en forbindelse.

```php
use Merql\Driver\DriverFactory;

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

Registrer en tilpasset driver til andre databaser:

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

---

## Filtre

### TableFilter

`Merql\Filter\TableFilter` inkluderer eller udelukker tabeller ved hjælp af globmønstre.

```php
use Merql\Filter\TableFilter;

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

### ColumnFilter

`Merql\Filter\ColumnFilter` fjerner bestemte kolonner fra snapshots.

```php
use Merql\Filter\ColumnFilter;

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

### RowFilter

`Merql\Filter\RowFilter` filtrerer rækker ved hjælp af en prædikatfunktion.

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

---

## Undtagelser

### SnapshotException

`Merql\Exceptions\SnapshotException` kastes, når et snapshot ikke kan findes eller har et ugyldigt navn.

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

### MergeException

`Merql\Exceptions\MergeException` kastes, når et påkrævet snapshot mangler til en sammenfletning.

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

### ConflictException

`Merql\Exceptions\ConflictException` kastes, når der forsøges at anvende et sammenfletningsresultat med uafklarede konflikter.

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

### SchemaException

`Merql\Exceptions\SchemaException` kastes (eller indsamles), når skemaerne er forskellige mellem snapshots.

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

Skemaforskelle stopper ikke sammenfletningen. De indsamles og kan ses via `$result->schemaMismatches()`.
