
# PHP API-referens

Fullständig referens för alla publika klasser och metoder i merql.

## Merql (fasad)

`Merql\Merql` är den statiska startpunkten för vanliga åtgärder. Den kapslar in klasser på lägre nivå och hanterar automatiskt lagring av ögonblicksbilder.

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

Initiera fasaden med en databasanslutning. Måste anropas före
`snapshot()`, `apply()` eller `applyGuarded()`; anropa den igen för att byta databas,
till exempel för att ta ögonblicksbilder av ours och theirs från två olika kopior. Utan en
`$driver` anropar den `DriverFactory::create()`, som ger ett undantag för andra motorer
än MySQL och SQLite, om inte en drivrutin har registrerats. Skicka med en drivrutin för att åsidosätta automatisk identifiering, eller en uppsättning identitetsregler för att åsidosätta radidentiteten per tabell när ögonblicksbilden tas.

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

Fånga det aktuella databastillståndet och spara det på disk. Skicka med en array med tabellnamn för att ta ögonblicksbilder av specifika tabeller, eller utelämna den för att inkludera alla tabeller.

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

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

Läs in två namngivna ögonblicksbilder och beräkna ändringsuppsättningen mellan dem. Kräver inte `init()` eftersom den arbetar med sparade ögonblicksbilder.

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

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

Trevägssammanslagning av tre namngivna ögonblicksbilder. Returnerar ett resultat med konfliktfria åtgärder och eventuella konflikter. Sammanslagningen sker på kolumnnivå: fasaden skapar `ThreeWayMerge` utan en `CellMergeConfig`, så sammanslagning på cellnivå tillämpas inte. Använd [`ThreeWayMerge`](#threewaymerge) direkt för det.

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

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

Tvåvägssammanslagning: tillämpa ändringar på base. Motsvarar `merge($base, $base, $changes)`. Ger alltid ett konfliktfritt resultat eftersom inga samtidiga ändringar finns. Endast på kolumnnivå, liksom `merge()`.

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

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

Kör ett konfliktfritt sammanslagningsresultat mot databasen som skickats till `init()`. Kräver `init()`. Ger `ConflictException` om det finns olösta konflikter.

Varje åtgärd i resultatet körs, så hela resultatet från `merge()` kan tillämpas korrekt endast på en databas som fortfarande innehåller base-tillståndet. Om databasen redan innehåller en av sidorna tillämpar du de åtgärder som saknas där; se [Skyddad tillämpning](https://merql.inline0.dev/sv/docs/apply/guarded).

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

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

Bygg en sammanslagningsplan redo för UI utifrån tre namngivna ögonblicksbilder. Planen innehåller stabila
åtgärds-ID:n, ID:n för ändringsgrupper, åtgärdskontext, konflikter, schemaavvikelser, en sammanfattning, metadata och en planhash. Sammanslagningen sker på kolumnnivå, som i
`merge()`; skicka ett resultat som slagits samman på cellnivå till `MergePlanBuilder::build()` för
sammanslagning på cellnivå.

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

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

Tillämpa ett konfliktfritt sammanslagningsresultat med optimistiska förvillkor för aktiva rader. Uppdateringar och
borttagningar kräver att aktiva rader matchar `$expectedLive`; vid infogningar får målidentiteten inte redan finnas. `$expectedLive` är en ögonblicksbild av
måldatabasen (theirs, när ours förs över till databasen som theirs
hämtades från). Om en sats inte påverkar några rader återställs transaktionen.

```php
use Merql\Snapshot\SnapshotStore;

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

### `reset(): void`

Rensa de interna anslutnings- och Snapshotter-instanserna. Används främst i tester för att återställa tillståndet mellan körningar.

---

## Anslutning

`Merql\Connection` bygger anslutningsadaptrar med lämpliga standardinställningar. PDO
anslutningar använder undantag vid fel, associativt hämtningsläge, ingen emulering av förberedda satser
och strängkonverterade hämtade värden. mysqli-adaptern normaliserar hämtade värden
till samma format med sträng eller 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`

Skapar en SQLite-anslutning. Aktiverar automatiskt främmande nycklar 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`

Anslut med valfri PDO-DSN-sträng. merql levereras endast med drivrutiner för `mysql` och `sqlite`. Andra DSN:er öppnar en anslutning, men `Merql::init()`, `Snapshotter` och tillämparna ger `Unsupported database driver` om ingen drivrutin har registrerats med `DriverFactory::register()`.

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

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

Omsluter en befintlig PDO-instans.

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

Omsluter en befintlig mysqli-instans.

### Adapterkontrakt

Alla adaptrar implementerar `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()` och `scalar()` godtar positionella `list<scalar|null>`-
parametrar för platshållare av typen `?`. `query()` returnerar
`list<array<string, scalar|null>>`; värden normaliseras till samma
format med sträng eller null i PDO- och mysqli-adaptrarna.

---

## Snapshotter

`Merql\Snapshot\Snapshotter` fångar databastillståndet. Till skillnad från fasaden ger den dig full kontroll över filter och sparar inte automatiskt.

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

Drivrutinen identifieras automatiskt utifrån anslutningens identitet om den inte anges. Skicka med en
`IdentityRuleSet` när tabellspecifik radidentitet ska åsidosätta schemats
standardinställningar.

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

Fånga det aktuella tillståndet. Ange tabellnamn för att begränsa omfattningen. Skicka med filterobjekt för att utesluta tabeller, kolumner eller rader.

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

Fånga fysiska tabeller under kanoniska tabellnamn. Använd detta när samma
logiska tabell har olika fysiska namn i olika miljöer.

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

Den resulterande ögonblicksbilden lagrar kanoniska tabellnamn samtidigt som
fysiskt tabellschema, konfigurerade identitetskolumner, radnycklar och filter bevaras.

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

Bygg en ögonblicksbild från rådata utan databasanslutning. Användbart vid testning eller när data importeras från en annan källa.

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

---

## Ögonblicksbild

`Merql\Snapshot\Snapshot` är ett skrivskyddat värdeobjekt som representerar ett fullständigt databastillstånd.

```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` innehåller en tabells rader, fingeravtryck och schema.

```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` är indata för `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` definierar kolumnerna som används för att skapa radnycklar.

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

Lös standardregeln för identitet utifrån schemat: primärnyckel, första unika nyckel,
därefter reservlösning med innehåll.

### `toArray(): array`

Serialisera regeln som `['type' => string, 'columns' => list<string>]`.

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

Återskapa en regel från serialiserade data.

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` lagrar tabellspecifika åsidosättningar av identiteten.

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

Returnera en ny regeluppsättning med en tillagd åsidosättning för en tabell.

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

Returnera poster av typen `IdentityConflict` när flera rader ger samma nyckel för en
regel.

### `toArray(): array`

Serialisera tabellregler som en JSON-säker array.

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

Återskapa en regeluppsättning från serialiserade data.

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` sparar och läser in ögonblicksbilder som filer av typen JSON.

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

Ange lagringskatalogen. Standard är `.merql/snapshots`.

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

Skriv en ögonblicksbild till disk.

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

Läs in en ögonblicksbild efter namn. Ger `SnapshotException` om den inte hittas.

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

Kontrollera om en ögonblicksbildsfil finns.

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

Ta bort en ögonblicksbildsfil.

---

## Differerare

`Merql\Diff\Differ` jämför två ögonblicksbilder och skapar en ändringsuppsättning.

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

Jämför två värden genom att konvertera dem till strängar. NULL behandlas som ett eget värde: `NULL === NULL` är sant, `NULL === ''` är falskt.

---

## Ändringsuppsättning

`Merql\Diff\Changeset` är en skrivskyddad samling åtgärder på radnivå.

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

Skrivskyddade värdeobjekt som representerar enskilda radändringar.

### 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` representerar en enskild kolumnändring i en raduppdatering.

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

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` är den centrala sammanslagningsalgoritmen.

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

Skicka med en `CellMergeConfig` för att aktivera sammanslagning på cellnivå för kolumnerna TEXT och JSON.

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

Utför en trevägssammanslagning. Beräknar ändringsuppsättningar internt (base till ours, base till theirs) och slår samman dem.

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

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

Genväg för tvåvägssammanslagning. Motsvarar `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

Returnerar `SchemaException[]` för alla schemaavvikelser som upptäcktes vid den senaste sammanslagningen (kolumner som lagts till eller tagits bort mellan ögonblicksbilder).

---

## MergeResult

`Merql\Merge\MergeResult` är ett skrivskyddat värdeobjekt som innehåller resultatet av sammanslagningen.

```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` är en serialiserbar granskningsmodell för en sammanslagning.

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

Använd `MergePlanSerializer::toJson()` och `::fromJson()` för stabila JSON-rundresor.

## Urval

`ChangeGroupSelection` lagrar ID:n för grupper med förberedda ändringar. Konvertera det till en `OperationSelection` när du bygger valda sammanslagningsresultat eller återställningsplaner.

```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` lagrar inversa åtgärder och förväntningar på avvikelser för en vald tillämpning.

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

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

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

Använd `RollbackDrift` innan återställningen tillämpas och sedan `RollbackPlanApplier` för att köra de inversa åtgärderna.

---

## MergeOperation

`Merql\Merge\MergeOperation` är en enda löst åtgärd från sammanslagningen.

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

Typkonstanter:

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

---

## Konflikt

`Merql\Merge\Conflict` representerar en olöst sammanslagningskonflikt.

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

| Typ | Betydelse |
|---|---|
| `update_update` | Båda sidorna ändrade samma kolumn till olika värden |
| `update_delete` | Vår sida uppdaterade en rad som deras sida tog bort |
| `delete_update` | Vår sida tog bort en rad som deras sida uppdaterade |
| `insert_insert` | Båda sidorna infogade en rad med samma primärnyckel |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` tillämpar en lösningspolicy på alla konflikter i ett sammanslagningsresultat.

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

Returnerar en ny `MergeResult` med konflikter lösta enligt policyn.

```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` är en enum med tre fall.

```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` utför sammanslagningslogik per kolumn för en enskild rad. Används internt av `ThreeWayMerge`.

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

Returnerar `['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)
```

---

## Tillämpare

`Merql\Apply\Applier` kör ett sammanslagningsresultat mot en databas.

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

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

Kör alla åtgärder i en enda transaktion. Återställer transaktionen vid fel och rapporterar felet i `ApplyResult::errors()` i stället för att kasta ett undantag. Kastar `ConflictException` om resultatet har olösta 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());
}
```

Sortering efter främmande nycklar hanteras automatiskt: infogningar sorteras efter beroenden och borttagningar i omvänd beroendeordning.

---

## ApplyResult

`Merql\Apply\ApplyResult` är ett skrivskyddat värdeobjekt som returneras av tillämparen.

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

Efter en återställd tillämpning räknar `rowsAffected()` fortfarande med de satser som kördes före felet ([#4](https://github.com/inline0/merql/issues/4)). Kontrollera `hasErrors()` innan du litar på antalet.

---

## DryRun

`Merql\Apply\DryRun` genererar människoläsbara SQL utan att köra något.

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

Returnerar en lista med SQL-strängar där värdena är infogade (endast för 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` genererar parameteriserade SQL-satser. Används internt av både `Applier` och `DryRun`.

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

Returnerar `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` konfigurerar sammanslagningsstrategier på cellnivå för specifika kolumner eller kolumntyper.

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

Hjälpfabrik som tilldelar `TextCellMerger` till kolumner av typen TEXT/LONGTEXT och `JsonCellMerger` till kolumner av typen JSON.

```php
use Merql\CellMerge\CellMergeConfig;

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

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

Tilldela en sammanslagare till en specifik kolumn. Använd `table.column` för fullständigt kvalificerade namn eller bara `column` för alla 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`

Tilldela en sammanslagare till alla kolumner som matchar ett typmönster (skiftlägesokänslig delsträngsmatchning).

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

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

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

Slå upp sammanslagaren för en viss tabell, kolumn och typ. Prioritet: kvalificerat kolumnnamn, okvalificerat kolumnnamn, typmönster, standard (opak).

---

## CellMerger (gränssnitt)

`Merql\CellMerge\CellMerger` är gränssnittet för sammanslagningsstrategier på cellnivå.

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

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

Tre inbyggda implementationer tillhandahålls.

### TextCellMerger

`Merql\CellMerge\TextCellMerger` sammanfogar flerradig text med en trevägs diff rad för rad (samma algoritm som git använder för filinnehåll). Om båda sidorna redigerar olika rader blir sammanslagningen konfliktfri. Om båda sidorna redigerar samma rad på olika sätt rapporteras 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` sammanfogar JSON-värden genom att jämföra nycklar på toppnivå var för sig. Varje nyckel sammanfogas med trevägslogik. Nästlade objekt jämförs som opaka JSON-strängar (de sammanfogas inte 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` är standardalternativet. Det behandlar värden som opaka strängar och rapporterar alltid en konflikt när de skiljer sig åt.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` är returtypen för alla cellsammanslagare.

```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` är en skrivskyddad representation av tabellstrukturen.

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

---

## Drivrutinssystem

merql använder ett drivrutinssystem med utbytbara drivrutiner för att abstrahera databasspecifika åtgärder. Drivrutiner identifieras automatiskt utifrån anslutningens drivrutinsidentitet.

### Driver (gränssnitt)

`Merql\Driver\Driver` definierar kontraktet som alla databast drivrutiner implementerar.

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

Inbyggda implementationer: `MysqlDriver` och `SqliteDriver`.

### DriverFactory

`Merql\Driver\DriverFactory` identifierar automatiskt drivrutinen från en anslutning.

```php
use Merql\Driver\DriverFactory;

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

Registrera en anpassad drivrutin för andra databaser:

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

---

## Filter

### TableFilter

`Merql\Filter\TableFilter` inkluderar eller exkluderar tabeller med globmönster.

```php
use Merql\Filter\TableFilter;

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

### ColumnFilter

`Merql\Filter\ColumnFilter` tar bort specifika kolumner från ögonblicksbilder.

```php
use Merql\Filter\ColumnFilter;

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

### RowFilter

`Merql\Filter\RowFilter` filtrerar rader med en predikatfunktion.

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

---

## Undantag

### SnapshotException

`Merql\Exceptions\SnapshotException` kastas när en ögonblicksbild inte kan hittas eller har ett ogiltigt namn.

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

### MergeException

`Merql\Exceptions\MergeException` kastas när en sammanslagning saknar en nödvändig ögonblicksbild.

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

### ConflictException

`Merql\Exceptions\ConflictException` kastas när ett sammanslagningsresultat med olösta konflikter försöker tillämpas.

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

### SchemaException

`Merql\Exceptions\SchemaException` kastas (eller samlas in) när scheman skiljer sig mellan ögonblicksbilder.

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

Schemainkonsekvenser stoppar inte sammanslagningen. De samlas in och är tillgängliga via `$result->schemaMismatches()` för granskning.
