
# Справка за 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()`; извикайте го отново, за да смените базите данни, например за да направите моментни снимки на нашата и тяхната версия от две различни копия. Без `$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/bg/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` е моментна снимка на целевата база данни (тяхната версия, когато прехвърляте нашата към базата, от която е направена тяхната моментна снимка). Всеки оператор, който не засяга нито един ред, води до връщане на транзакцията.

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

Свържете се чрез произволен PDO DSN низ. 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` е истина, `NULL === ''` е лъжа.

---

## Набор от промени

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

Извършва тристранно сливане. Изчислява вътрешно наборите от промени (от основата към нашата версия и от основата към тяхната) и ги слива.

```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` | Нашата страна е актуализирала ред, който тяхната е изтрила |
| `delete_update` | Нашата страна е изтрила ред, който тяхната е актуализирала |
| `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`

Търси обединителя за дадена таблица, колона и тип. Приоритет: квалифицирано име на колона, неквалифицирано име на колона, шаблон за тип, по подразбиране (непрозрачен).

---

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