
# Referenční dokumentace PHP API

Úplná referenční dokumentace všech veřejných tříd a metod v merql.

## Merql (fasáda)

`Merql\Merql` je statickým vstupním bodem pro běžné operace. Zastřešuje nízkoúrovňové třídy a automaticky spravuje trvalé ukládání snímků.

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

Inicializujte fasádu připojením k databázi. Je nutné ji zavolat před `snapshot()`, `apply()` nebo `applyGuarded()`; zavolejte ji znovu při přepnutí databáze, například při pořizování snímků naší a jejich kopie ze dvou různých kopií. Bez `$driver` zavolá `DriverFactory::create()`, který vyvolá výjimku pro jiné databázové stroje než MySQL a SQLite, pokud není zaregistrován ovladač. Předejte ovladač, chcete-li přepsat automatickou detekci, nebo sadu pravidel identity, chcete-li při pořizování snímku přepsat identitu řádků pro jednotlivé tabulky.

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

Zachyťte aktuální stav databáze a uložte ho na disk. Předejte pole názvů tabulek, chcete-li pořídit snímek konkrétních tabulek; vynecháte-li ho, zahrnou se všechny tabulky.

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

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

Načte dva pojmenované snímky a vypočítá mezi nimi sadu změn. `init()` není potřeba, protože se pracuje s uloženými snímky.

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

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

Třístranné sloučení tří pojmenovaných snímků. Vrátí výsledek obsahující čisté operace a případné konflikty. Sloučení probíhá na úrovni sloupců: fasáda vytvoří `ThreeWayMerge` bez `CellMergeConfig`, takže se sloučení na úrovni buněk neprovede. Pro toto použijte přímo [`ThreeWayMerge`](#threewaymerge).

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

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

Dvoustranné sloučení: použije změny na základ. Odpovídá `merge($base, $base, $changes)`. Výsledek je vždy čistý, protože nedochází k souběžným úpravám. Stejně jako `merge()` probíhá pouze na úrovni sloupců.

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

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

Provede čistý výsledek sloučení nad databází předanou do `init()`. Vyžaduje `init()`. Pokud zůstanou nevyřešené konflikty, vyvolá `ConflictException`.

Provedou se všechny operace ve výsledku, takže celý výsledek `merge()` lze čistě použít pouze v databázi, která stále obsahuje základní stav. Pokud databáze již obsahuje jednu stranu, použijte výběr operací, které v ní chybí; viz [Řízené použití](https://merql.inline0.dev/cs/docs/apply/guarded).

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

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

Vytvoří plán sloučení připravený pro UI ze tří pojmenovaných snímků. Plán obsahuje stabilní ID operací, ID skupin změn, kontext operací, konflikty, neshody schématu, souhrn, metadata a hash plánu. Sloučení probíhá na úrovni sloupců stejně jako v `merge()`; pro sloučení na úrovni buněk předejte výsledek sloučení buněk do `MergePlanBuilder::build()`.

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

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

Použije čistý výsledek sloučení s optimistickými předpoklady pro aktuální řádky. U aktualizací a mazání musí aktuální živé řádky odpovídat `$expectedLive`; u vkládání nesmí cílová identita již existovat. `$expectedLive` je snímek cílové databáze (jejich strany, pokud se naše změny přenášejí do databáze, z níž byl pořízen jejich snímek). Každý příkaz, který neovlivní žádné řádky, vrátí transakci zpět.

```php
use Merql\Snapshot\SnapshotStore;

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

### `reset(): void`

Vymaže interní instance připojení a nástroje Snapshotter. Používá se hlavně v testech k resetování stavu mezi běhy.

---

## Připojení

`Merql\Connection` vytváří adaptéry připojení s rozumným výchozím nastavením. Připojení PDO používají při chybě výjimky, asociativní režim načítání, žádné emulované připravené dotazy a načtené hodnoty převádějí na řetězce. Adaptér mysqli normalizuje načtené hodnoty do stejného tvaru řetězec nebo 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`

Vytvoří připojení SQLite. Automaticky zapne cizí klíče prostřednictvím `PRAGMA foreign_keys = ON`.

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

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

Připojte se pomocí libovolného řetězce DSN pro PDO. merql obsahuje ovladače pouze pro `mysql` a `sqlite`; ostatní DSN připojení otevřou, ale `Merql::init()`, `Snapshotter` a aplikátory vyvolají `Unsupported database driver`, pokud není ovladač zaregistrován pomocí `DriverFactory::register()`.

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

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

Obalí existující instanci PDO.

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

Obalí existující instanci mysqli.

### Kontrakt adaptéru

Všechny adaptéry implementují `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()` a `scalar()` přijímají poziční parametry `list<scalar|null>` pro zástupné symboly `?`. `query()` vrací `list<array<string, scalar|null>>`; hodnoty se normalizují do stejného tvaru řetězec nebo null u adaptérů PDO i mysqli.

---

## Snapshotter

`Merql\Snapshot\Snapshotter` zachycuje stav databáze. Na rozdíl od fasády vám dává plnou kontrolu nad filtry a neukládá automaticky výsledek.

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

Ovladač se automaticky rozpozná podle identity připojení, pokud jej nezadáte. Předejte `IdentityRuleSet`, chcete-li přepsat výchozí nastavení schématu pro identitu řádků v konkrétních tabulkách.

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

Zachytí aktuální stav. Předejte názvy tabulek a omezte tak rozsah. Předejte objekty filtrů a vylučte tabulky, sloupce nebo řádky.

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

Zachytí fyzické tabulky pod kanonickými názvy tabulek. Použijte to, když má stejná logická tabulka v různých prostředích odlišné fyzické názvy.

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

Výsledný snímek ukládá kanonické názvy tabulek a zároveň zachovává schéma fyzických tabulek, nakonfigurované sloupce identity, klíče řádků a filtry.

### `fromData(string $name, array $tableData): Snapshot` (statická metoda)

Vytvoří snímek z nezpracovaných dat bez připojení k databázi. Hodí se pro testování nebo import dat z jiného zdroje.

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

---

## Snímek

`Merql\Snapshot\Snapshot` je objekt pouze pro čtení, který reprezentuje úplný stav databáze.

```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` obsahuje řádky, otisky a schéma jedné tabulky.

```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` představuje vstupní data pro `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` definuje sloupce použité k vytvoření klíčů řádků.

```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` (statická metoda)

Vyřeší výchozí pravidlo identity ze schématu: primární klíč, první jedinečný klíč, pak náhradní řešení podle obsahu.

### `toArray(): array`

Serializuje pravidlo jako `['type' => string, 'columns' => list<string>]`.

### `fromArray(array $data): IdentityRule` (statická metoda)

Načte pravidlo ze serializovaných dat.

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` ukládá přepsání identity pro jednotlivé tabulky.

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

Vrátí novou sadu pravidel s přidaným přepsáním pro jednu tabulku.

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

Vrátí položky `IdentityConflict`, pokud více řádků vytvoří pro určité pravidlo stejný klíč.

### `toArray(): array`

Serializuje pravidla tabulek do pole bezpečného pro JSON.

### `fromArray(array $data): IdentityRuleSet` (statická metoda)

Načte sadu pravidel ze serializovaných dat.

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` ukládá snímky jako soubory JSON a načítá je z nich.

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

Nastaví adresář úložiště. Výchozí hodnota je `.merql/snapshots`.

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

Zapíše snímek na disk.

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

Načte snímek podle názvu. Pokud jej nenajde, vyvolá `SnapshotException`.

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

Ověří, zda soubor se snímkem existuje.

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

Odstraní soubor se snímkem.

---

## Nástroj pro zjišťování rozdílů

`Merql\Diff\Differ` porovná dva snímky a vytvoří sadu změn.

### `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` (statická metoda)

Porovná dvě hodnoty po převedení na řetězec. NULL se považuje za samostatnou hodnotu: `NULL === NULL` platí, `NULL === ''` neplatí.

---

## Sada změn

`Merql\Diff\Changeset` je kolekce operací na úrovni řádků pouze pro čtení.

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

Objekty pouze pro čtení představující jednotlivé změny řádků.

### 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` představuje změnu jednoho sloupce v rámci aktualizace řádku.

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

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` je základním algoritmem sloučení.

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

Předejte `CellMergeConfig` a povolte sloučení buněk pro sloupce TEXT a 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`

Provede třístranné sloučení. Interně vypočítá sady změn (od základu k naší straně a od základu k jejich straně) a sloučí je.

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

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

Zkratka pro dvoustranné sloučení. Odpovídá `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

Vrátí `SchemaException[]` pro všechny rozdíly schématu zjištěné při posledním sloučení (sloupce přidané nebo odebrané mezi snímky).

---

## MergeResult

`Merql\Merge\MergeResult` je objekt pouze pro čtení obsahující výsledek sloučení.

```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` je serializovatelný model sloučení určený ke kontrole.

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

Pro stabilní průběhy JSON použijte `MergePlanSerializer::toJson()` a `::fromJson()`.

## Výběry

`ChangeGroupSelection` ukládá ID připravených skupin změn. Převeďte jej na `OperationSelection` při vytváření vybraných výsledků sloučení nebo plánů vrácení změn.

```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` ukládá inverzní operace a očekávané odchylky pro vybrané použití změn.

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

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

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

Před vrácením změn použijte `RollbackDrift` a pak spusťte inverzní operace pomocí `RollbackPlanApplier`.

---

## MergeOperation

`Merql\Merge\MergeOperation` je jedna vyřešená operace ze sloučení.

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

Konstanty typů:

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

---

## Konflikt

`Merql\Merge\Conflict` představuje nevyřešený konflikt sloučení.

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

### Typy konfliktů

| Typ | Význam |
|---|---|
| `update_update` | Obě strany změnily stejný sloupec na různé hodnoty |
| `update_delete` | Naše strana aktualizovala řádek, který jejich strana smazala |
| `delete_update` | Naše strana smazala řádek, který jejich strana aktualizovala |
| `insert_insert` | Obě strany vložily řádek se stejným primárním klíčem |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` použije zásadu řešení na všechny konflikty ve výsledku sloučení.

### `resolve(MergeResult $result, ConflictPolicy $policy): MergeResult` (statická metoda)

Vrátí nové `MergeResult` s konflikty vyřešenými podle zásady.

```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` je výčtový typ se třemi případy.

```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` provádí logiku sloučení jednotlivých sloupců jednoho řádku. Interně jej používá `ThreeWayMerge`.

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

Vrátí `['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)
```

---

## Aplikátor

`Merql\Apply\Applier` provede výsledek sloučení nad databází.

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

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

Provede všechny operace v jediné transakci. Při jakékoli chybě transakci vrátí zpět a nahlásí ji v `ApplyResult::errors()`, místo aby vyvolal výjimku. Pokud výsledek obsahuje nevyřešené konflikty, vyvolá `ConflictException`.

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

Pořadí cizích klíčů se řeší automaticky: vkládání se řadí podle závislostí a mazání v opačném pořadí závislostí.

---

## ApplyResult

`Merql\Apply\ApplyResult` je objekt pouze pro čtení, který vrací aplikátor.

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

Po vrácení transakce zpět stále `rowsAffected()` počítá příkazy spuštěné před selháním ([#4](https://github.com/inline0/merql/issues/4)). Než začnete počtu důvěřovat, zkontrolujte `hasErrors()`.

---

## DryRun

`Merql\Apply\DryRun` vytváří čitelné SQL, aniž cokoli provede.

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

Vrátí seznam řetězců SQL s vloženými hodnotami (pouze pro zobrazení).

```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` vytváří parametrizované příkazy SQL. Interně je používají jak `Applier`, tak `DryRun`.

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

Vrátí `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` konfiguruje strategie sloučení buněk pro konkrétní sloupce nebo typy sloupců.

### `auto(): self` (statická metoda)

Pomocná tovární metoda, která přiřadí `TextCellMerger` sloupcům TEXT/LONGTEXT a `JsonCellMerger` sloupcům JSON.

```php
use Merql\CellMerge\CellMergeConfig;

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

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

Přiřadí slučovač konkrétnímu sloupci. Pro kvalifikované názvy použijte `table.column`, případně `column` pro všechny tabulky.

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

Přiřadí slučovač všem sloupcům odpovídajícím vzoru typu (rozlišování podřetězce bez ohledu na velikost písmen).

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

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

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

Vyhledá slučovač pro zadanou tabulku, sloupec a typ. Priorita: kvalifikovaný název sloupce, nekvalifikovaný název sloupce, vzor typu, výchozí hodnota (opaque).

---

## CellMerger (rozhraní)

`Merql\CellMerge\CellMerger` je rozhraní pro strategie sloučení na úrovni buněk.

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

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

K dispozici jsou tři vestavěné implementace.

### TextCellMerger

`Merql\CellMerge\TextCellMerger` slučuje víceřádkový text pomocí třícestného diffu řádek po řádku (stejný algoritmus, jaký git používá pro obsah souborů). Když obě strany upraví různé řádky, sloučení proběhne bez konfliktu. Když obě strany různě upraví stejný řádek, oznámí 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` slučuje hodnoty JSON nezávislým porovnáním klíčů nejvyšší úrovně. Každý klíč se sloučí pomocí třístranné logiky. Vnořené objekty se porovnávají jako neprůhledné řetězce JSON (neprovádí se rekurzivní sloučení).

```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` je výchozí možnost. S hodnotami zachází jako s neprůhlednými řetězci a při každém rozdílu vždy nahlásí konflikt.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` je návratovým typem všech slučovačů buněk.

```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` je reprezentace struktury tabulky pouze pro čtení.

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

---

## Systém ovladačů

merql používá rozšiřitelný systém ovladačů, který abstrahuje operace specifické pro jednotlivé databáze. Ovladače se automaticky rozpoznávají podle identity ovladače připojení.

### Driver (rozhraní)

`Merql\Driver\Driver` definuje kontrakt, který implementují všechny databázové ovladače.

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

Vestavěné implementace: `MysqlDriver` a `SqliteDriver`.

### DriverFactory

`Merql\Driver\DriverFactory` automaticky rozpozná ovladač podle připojení.

```php
use Merql\Driver\DriverFactory;

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

Zaregistrujte vlastní ovladač pro jiné databáze:

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

---

## Filtry

### TableFilter

`Merql\Filter\TableFilter` zahrnuje nebo vylučuje tabulky pomocí vzorů glob.

```php
use Merql\Filter\TableFilter;

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

### ColumnFilter

`Merql\Filter\ColumnFilter` odstraňuje konkrétní sloupce ze snímků.

```php
use Merql\Filter\ColumnFilter;

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

### RowFilter

`Merql\Filter\RowFilter` filtruje řádky pomocí predikátové funkce.

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

---

## Výjimky

### SnapshotException

`Merql\Exceptions\SnapshotException` se vyvolá, když snímek nelze najít nebo má neplatný název.

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

### MergeException

`Merql\Exceptions\MergeException` se vyvolá, když pro sloučení chybí požadovaný snímek.

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

### ConflictException

`Merql\Exceptions\ConflictException` se vyvolá při pokusu použít výsledek sloučení, který obsahuje nevyřešené konflikty.

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

### SchemaException

`Merql\Exceptions\SchemaException` se vyvolá (nebo zachytí), když se schémata mezi snímky liší.

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

Neshody schémat sloučení nezastaví. Shromáždí se a lze je zkontrolovat pomocí `$result->schemaMismatches()`.
