
# Довідник PHP API

Повний довідник усіх публічних класів і методів у merql.

## Merql (фасад)

`Merql\Merql` — статична точка входу для типових операцій. Вона обгортає класи нижчого рівня та автоматично керує збереженням знімків.

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

Ініціалізуйте фасад, передавши підключення до бази даних. Це потрібно зробити перед викликом `snapshot()`, `apply()` або `applyGuarded()`. Щоб перемкнути базу даних, викличте метод ще раз — наприклад, щоб створити знімки ours і theirs із двох різних копій. Без `$driver` метод викликає `DriverFactory::create()`, який завершується помилкою для рушіїв, відмінних від MySQL і SQLite, якщо не зареєстровано драйвер. Передайте драйвер, щоб перевизначити автоматичне виявлення, або набір правил ідентичності, щоб перевизначити ідентичність рядків у кожній таблиці під час знімання даних.

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

Зніміть поточний стан бази даних і збережіть його на диску. Передайте масив назв таблиць, щоб створити знімок лише для вказаних таблиць, або не передавайте його, щоб охопити всі таблиці.

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

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

Завантажте два іменовані знімки та обчисліть набір змін між ними. Для цього не потрібен `init()`, оскільки операція виконується на основі збережених знімків.

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

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

Тристороннє злиття трьох іменованих знімків. Повертає результат, що містить операції без конфліктів і всі конфлікти. Злиття виконується на рівні стовпців: фасад формує `ThreeWayMerge` без `CellMergeConfig`, тому злиття на рівні комірок не застосовується. Для цього безпосередньо використовуйте [`ThreeWayMerge`](#threewaymerge).

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

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

Двостороннє злиття: застосувати зміни до базового стану. Еквівалент `merge($base, $base, $changes)`. Завжди дає результат без конфліктів, оскільки паралельних змін немає. Лише на рівні стовпців, як і `merge()`.

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

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

Виконайте результат злиття без конфліктів у базі даних, переданій до `init()`. Потрібен `init()`. Викидає `ConflictException`, якщо залишаються невирішені конфлікти.

Виконуються всі операції результату, тому повний результат `merge()` можна успішно застосувати лише до бази даних, у якій досі зберігається базовий стан. Якщо база даних уже містить один із боків, застосуйте вибрані операції, яких їй бракує; див. [Захищене застосування](https://merql.inline0.dev/uk/docs/apply/guarded).

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

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

Створіть план злиття, готовий для UI, на основі трьох іменованих знімків. План містить стабільні
ідентифікатори операцій, ідентифікатори груп змін, контекст операцій, конфлікти, невідповідності схем,
зведення, метадані та хеш плану. Злиття виконується на рівні стовпців, як
у `merge()`; передайте результат злиття комірок до `MergePlanBuilder::build()` для
злиття на рівні комірок.

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

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

Застосуйте результат злиття без конфліктів з оптимістичними передумовами для активних рядків. Для оновлення та
видалення потрібна відповідність поточних активних рядків `$expectedLive`; для вставлення цільова сутність
не має вже існувати. `$expectedLive` — це знімок
цільової бази даних (сторона theirs, коли ours переноситься до бази даних, з якої було
зроблено знімок theirs). Якщо будь-який оператор змінює нуль рядків, транзакцію буде скасовано.

```php
use Merql\Snapshot\SnapshotStore;

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

### `reset(): void`

Очистіть внутрішні екземпляри з'єднання та Snapshotter. Використовується переважно в тестах для скидання стану між запусками.

---

## З'єднання

`Merql\Connection` створює адаптери з'єднання з оптимальними типовими параметрами. З'єднання PDO використовують винятки у разі помилки, режим отримання асоціативних масивів, справжню підготовку запитів і перетворення отриманих значень на рядки. Адаптер mysqli нормалізує отримані значення до такого самого формату: рядок або 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`

Створює з'єднання SQLite. Автоматично вмикає зовнішні ключі за допомогою `PRAGMA foreign_keys = ON`.

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

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

Підключайтеся за допомогою будь-якого рядка DSN PDO. merql постачається лише з драйверами для `mysql` та `sqlite`; з іншими DSN з'єднання встановлюється, але `Merql::init()`, `Snapshotter` та засоби застосування викидають `Unsupported database driver`, якщо драйвер не зареєстровано через `DriverFactory::register()`.

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

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

Обгортає наявний екземпляр PDO.

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

Обгортає наявний екземпляр mysqli.

### Контракт адаптера

Усі адаптери реалізують `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()` та `scalar()` приймають позиційні параметри `list<scalar|null>`
для заповнювачів `?`. `query()` повертає
`list<array<string, scalar|null>>`; значення нормалізуються до однакового
формату «рядок або null» в адаптерах PDO та mysqli.

---

## Створення знімків

`Merql\Snapshot\Snapshotter` фіксує стан бази даних. На відміну від фасаду, він дає повний контроль над фільтрами та не зберігає дані автоматично.

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

Якщо драйвер не вказано, він визначається автоматично за ідентифікатором з'єднання. Передайте
`IdentityRuleSet`, якщо ідентифікатори рядків для конкретних таблиць мають перевизначати
типові параметри схеми.

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

Зафіксуйте поточний стан. Передайте назви таблиць, щоб обмежити область дії. Передайте об'єкти фільтрів, щоб виключити таблиці, стовпці або рядки.

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

Зафіксуйте фізичні таблиці під канонічними назвами. Використовуйте цей варіант, коли та сама
логічна таблиця має різні фізичні назви в різних середовищах.

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

Отриманий знімок зберігає канонічні назви таблиць, водночас зберігаючи
фізичну схему таблиці, налаштовані стовпці ідентифікації, ключі рядків і фільтри.

### `fromData(string $name, array $tableData): Snapshot` (статичний метод)

Створює знімок із необроблених даних без підключення до бази даних. Зручно для тестування або імпорту даних з іншого джерела.

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

---

## Знімок

`Merql\Snapshot\Snapshot` — це об'єкт-значення лише для читання, що представляє повний стан бази даних.

```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` містить рядки, відбитки та схему однієї таблиці.

```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` — це вхідні дані для `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` визначає стовпці, які використовуються для формування ключів рядків.

```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` (статичний метод)

Визначає типове правило ідентифікації за схемою: первинний ключ, перший унікальний ключ,
потім запасний варіант на основі вмісту.

### `toArray(): array`

Серіалізує правило як `['type' => string, 'columns' => list<string>]`.

### `fromArray(array $data): IdentityRule` (статичний метод)

Відновлює правило із серіалізованих даних.

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` зберігає перевизначення ідентифікації для окремих таблиць.

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

Повертає новий набір правил з доданим перевизначенням для однієї таблиці.

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

Повертає записи `IdentityConflict`, якщо для правила кілька рядків утворюють однаковий ключ.

### `toArray(): array`

Серіалізує правила таблиць у вигляді масиву, сумісного з JSON.

### `fromArray(array $data): IdentityRuleSet` (статичний метод)

Відновлює набір правил із серіалізованих даних.

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` зберігає знімки у файлах JSON та завантажує їх.

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

Вказує каталог зберігання. Типове значення — `.merql/snapshots`.

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

Записує знімок на диск.

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

Завантажує знімок за назвою. Викидає `SnapshotException`, якщо його не знайдено.

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

Перевіряє, чи існує файл знімка.

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

Видаляє файл знімка.

---

## Порівнювач

`Merql\Diff\Differ` порівнює два знімки та створює набір змін.

### `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` (статичний метод)

Порівнює два значення, приводячи їх до рядків. NULL вважається окремим значенням: `NULL === NULL` — true, `NULL === ''` — false.

---

## Набір змін

`Merql\Diff\Changeset` — це колекція операцій на рівні рядків, доступна лише для читання.

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

Об'єкти-значення лише для читання, що представляють окремі зміни рядків.

### 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` представляє одну зміну стовпця під час оновлення рядка.

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

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` — основний алгоритм злиття.

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

Передайте `CellMergeConfig`, щоб увімкнути злиття на рівні комірок для стовпців TEXT і 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`

Виконує тристороннє злиття. Внутрішньо обчислює набори змін (від базового стану до ours, від базового стану до theirs) та об'єднує їх.

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

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

Скорочений варіант двостороннього злиття. Еквівалент `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

Повертає `SchemaException[]` для всіх розбіжностей схем, виявлених під час останнього злиття (стовпці додані або видалені між знімками).

---

## MergeResult

`Merql\Merge\MergeResult` — це об'єкт-значення лише для читання, що містить результат злиття.

```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` — це серіалізована модель злиття для перевірки.

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

Використовуйте `MergePlanSerializer::toJson()` і `::fromJson()` для стабільних циклічних перетворень JSON.

## Вибірки

`ChangeGroupSelection` зберігає ідентифікатори підготовлених груп змін. Під час створення результатів злиття для вибраних елементів або планів відкату перетворіть його на `OperationSelection`.

```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` зберігає зворотні операції та очікування щодо відхилень
для вибіркового застосування.

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

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

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

Використовуйте `RollbackDrift` перед застосуванням відкату, а потім `RollbackPlanApplier` для виконання
зворотних операцій.

---

## MergeOperation

`Merql\Merge\MergeOperation` — це одна визначена операція з результату злиття.

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

Константи типів:

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

---

## Конфлікт

`Merql\Merge\Conflict` представляє невирішений конфлікт злиття.

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

### Типи конфліктів

| Тип | Значення |
|---|---|
| `update_update` | Обидві сторони змінили той самий стовпець на різні значення |
| `update_delete` | Сторона ours оновила рядок, який сторона theirs видалила |
| `delete_update` | Сторона ours видалила рядок, який сторона theirs оновила |
| `insert_insert` | Обидві сторони вставили рядок з однаковим первинним ключем |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` застосовує політику вирішення до всіх конфліктів у результаті злиття.

### `resolve(MergeResult $result, ConflictPolicy $policy): MergeResult` (статичний метод)

Повертає новий `MergeResult` із конфліктами, вирішеними відповідно до політики.

```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` — це enum із трьома варіантами.

```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` виконує логіку злиття окремих стовпців одного рядка. Внутрішньо використовується `ThreeWayMerge`.

### `merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array` (статичний метод)

Повертає `['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)
```

---

## Застосовувач

`Merql\Apply\Applier` застосовує результат злиття до бази даних.

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

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

Виконує всі операції в одній транзакції. У разі помилки відкочує транзакцію та повідомляє про неї в `ApplyResult::errors()`, а не викидає виняток. Викидає `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());
}
```

Порядок зовнішніх ключів обробляється автоматично: вставлення сортуються за залежностями, а видалення — у зворотному порядку залежностей.

---

## ApplyResult

`Merql\Apply\ApplyResult` — це об'єкт-значення лише для читання, який повертає засіб застосування.

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

Після скасованого застосування `rowsAffected()` усе ще враховує оператори, виконані до помилки ([#4](https://github.com/inline0/merql/issues/4)). Перш ніж покладатися на це число, перевірте `hasErrors()`.

---

## DryRun

`Merql\Apply\DryRun` формує зрозумілі людині оператори SQL, не виконуючи жодних дій.

### `generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array` (статичний метод)

Повертає список рядків SQL зі вбудованими значеннями (лише для відображення).

```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` формує параметризовані оператори SQL. Внутрішньо використовується і `Applier`, і `DryRun`.

### `generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array` (статичний метод)

Повертає `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` налаштовує стратегії злиття на рівні комірок для окремих стовпців або типів стовпців.

### `auto(): self` (статичний метод)

Фабричний метод для зручного призначення `TextCellMerger` стовпцям TEXT/LONGTEXT і `JsonCellMerger` стовпцям JSON.

```php
use Merql\CellMerge\CellMergeConfig;

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

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

Призначає об'єднувач конкретному стовпцю. Використовуйте `table.column` для повних назв або лише `column` для всіх таблиць.

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

Призначає об'єднувач усім стовпцям, що відповідають шаблону типу (регістронезалежний пошук підрядка).

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

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

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

Шукає об'єднувач для заданих таблиці, стовпця й типу. Пріоритет: повна назва стовпця, коротка назва стовпця, шаблон типу, типовий варіант (opaque).

---

## CellMerger (інтерфейс)

`Merql\CellMerge\CellMerger` — це інтерфейс стратегій злиття на рівні комірок.

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

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

Надаються три вбудовані реалізації.

### TextCellMerger

`Merql\CellMerge\TextCellMerger` об'єднує багаторядковий текст за допомогою тристороннього порівняння рядок за рядком (той самий алгоритм, який git використовує для вмісту файлів). Якщо обидві сторони редагують різні рядки, конфлікту немає. Якщо обидві сторони по-різному редагують той самий рядок, повідомляється про конфлікт.

```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` об'єднує значення JSON, порівнюючи ключі верхнього рівня незалежно один від одного. Кожен ключ об'єднується за тристоронньою логікою. Вкладені об'єкти порівнюються як непрозорі рядки JSON (рекурсивне злиття не виконується).

```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` — типовий варіант. Він сприймає значення як непрозорі рядки та завжди повідомляє про конфлікт, якщо вони відрізняються.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` — це тип повернення всіх засобів злиття комірок.

```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` — це доступне лише для читання представлення структури таблиці.

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

---

## Система драйверів

merql використовує систему драйверів із можливістю підключення, щоб абстрагувати операції, специфічні для баз даних. Драйвери автоматично визначаються за ідентифікатором драйвера з'єднання.

### Driver (інтерфейс)

`Merql\Driver\Driver` визначає контракт, якого дотримуються всі драйвери баз даних.

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

Вбудовані реалізації: `MysqlDriver` і `SqliteDriver`.

### DriverFactory

`Merql\Driver\DriverFactory` автоматично визначає драйвер за з'єднанням.

```php
use Merql\Driver\DriverFactory;

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

Зареєструйте власний драйвер для інших баз даних:

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

---

## Фільтри

### TableFilter

`Merql\Filter\TableFilter` включає або виключає таблиці за шаблонами glob.

```php
use Merql\Filter\TableFilter;

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

### ColumnFilter

`Merql\Filter\ColumnFilter` вилучає окремі стовпці зі знімків.

```php
use Merql\Filter\ColumnFilter;

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

### RowFilter

`Merql\Filter\RowFilter` фільтрує рядки за допомогою функції-предиката.

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

---

## Винятки

### SnapshotException

`Merql\Exceptions\SnapshotException` викидається, якщо знімок не знайдено або його назва недійсна.

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

### MergeException

`Merql\Exceptions\MergeException` викидається, якщо для злиття бракує обов'язкового знімка.

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

### ConflictException

`Merql\Exceptions\ConflictException` викидається під час спроби застосувати результат злиття з невирішеними конфліктами.

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

### SchemaException

`Merql\Exceptions\SchemaException` викидається (або додається до звіту), якщо схеми знімків відрізняються.

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

Невідповідності схем не зупиняють злиття. Вони збираються та доступні для перевірки через `$result->schemaMismatches()`.
