
# PHP API Dokumentacja referencyjna

Pełna dokumentacja wszystkich publicznych klas i metod w merql.

## Merql (fasada)

`Merql\Merql` to statyczny punkt wejścia do typowych operacji. Opakowuje klasy niższego poziomu i automatycznie zarządza trwałym zapisem migawek.

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

Zainicjuj fasadę, przekazując połączenie z bazą danych. Należy wywołać tę metodę przed
`snapshot()`, `apply()` lub `applyGuarded()`; wywołaj ją ponownie, aby przełączyć bazy danych,
na przykład w celu utworzenia migawek naszej i ich strony z dwóch różnych kopii. Bez
`$driver` metoda wywołuje `DriverFactory::create()`, które zgłasza wyjątek dla silników innych
niż MySQL i SQLite, chyba że zarejestrowano sterownik. Przekaż sterownik, aby zastąpić automatyczne wykrywanie, albo zestaw reguł tożsamości, aby zastąpić tożsamość wierszy dla poszczególnych tabel podczas przechwytywania migawki.

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

Przechwyć bieżący stan bazy danych i zapisz go na dysku. Przekaż tablicę nazw tabel, aby utworzyć migawki wybranych tabel, albo pomiń ten argument, aby uwzględnić wszystkie tabele.

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

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

Wczytaj dwie nazwane migawki i oblicz między nimi zestaw zmian. Nie wymaga `init()`, ponieważ korzysta z zapisanych migawek.

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

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

Scal trzy nazwane migawki. Zwraca wynik zawierający czyste operacje i ewentualne konflikty. Scala na poziomie kolumn: fasada tworzy `ThreeWayMerge` bez `CellMergeConfig`, więc scalanie na poziomie komórek nie jest stosowane. W tym celu użyj bezpośrednio [`ThreeWayMerge`](#threewaymerge).

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

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

Scalanie dwukierunkowe: zastosuj zmiany do bazy. Równoważne `merge($base, $base, $changes)`. Zawsze daje czysty wynik, ponieważ nie ma równoczesnych edycji. Tylko na poziomie kolumn, podobnie jak `merge()`.

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

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

Wykonaj czysty wynik scalania na bazie danych przekazanej do `init()`. Wymaga `init()`. Zgłasza `ConflictException`, jeśli pozostają nierozwiązane konflikty.

Wykonywana jest każda operacja z wyniku, więc pełny wynik `merge()` można zastosować bez konfliktów tylko do bazy danych, która nadal zawiera stan bazowy. Jeśli baza zawiera już stan jednej ze stron, zastosuj wybrane operacje, których jej brakuje; zobacz [Guarded Apply](https://merql.inline0.dev/pl/docs/apply/guarded).

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

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

Utwórz plan scalania gotowy do UI na podstawie trzech nazwanych migawek. Plan obejmuje stabilne
identyfikatory operacji, identyfikatory grup zmian, kontekst operacji, konflikty, niezgodności schematów,
podsumowanie, metadane i skrót planu. Scalanie odbywa się na poziomie kolumn, jak
w `merge()`; przekaż wynik scalony na poziomie komórek do `MergePlanBuilder::build()`, aby
scalić na poziomie komórek.

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

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

Zastosuj czysty wynik scalania z optymistycznymi warunkami wstępnymi dla aktywnych wierszy. Aktualizacje i
usunięcia wymagają, aby bieżące aktywne wiersze odpowiadały `$expectedLive`; w przypadku wstawień
wymagane jest, aby docelowa tożsamość jeszcze nie istniała. `$expectedLive` to migawka
 docelowej bazy danych (ich strony, gdy przenosimy naszą stronę do bazy, z której
utworzono ich migawkę). Każda instrukcja, która zmieni zero wierszy, powoduje wycofanie transakcji.

```php
use Merql\Snapshot\SnapshotStore;

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

### `reset(): void`

Wyczyść wewnętrzne instancje połączenia i modułu Snapshotter. Służy głównie do resetowania stanu między uruchomieniami testów.

---

## Połączenie

`Merql\Connection` tworzy adaptery połączeń z rozsądnymi ustawieniami domyślnymi. Połączenia PDO zgłaszają wyjątki w razie błędu, używają asocjacyjnego trybu pobierania, wyłączają emulowane przygotowywanie zapytań i zwracają pobrane wartości jako ciągi znaków. Adapter mysqli normalizuje pobrane wartości do takiej samej postaci ciągu znaków lub 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`

Tworzy połączenie SQLite. Automatycznie włącza klucze obce za pomocą `PRAGMA foreign_keys = ON`.

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

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

Nawiąż połączenie, używając dowolnego ciągu DSN PDO. merql zawiera sterowniki tylko dla `mysql` i `sqlite`; inne DSN nawiązują połączenie, ale `Merql::init()`, `Snapshotter` i moduły stosujące zmiany zgłaszają `Unsupported database driver`, chyba że sterownik został zarejestrowany przez `DriverFactory::register()`.

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

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

Opakuj istniejącą instancję PDO.

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

Opakuj istniejącą instancję mysqli.

### Kontrakt adaptera

Wszystkie adaptery 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()` i `scalar()` przyjmują pozycyjne parametry `list<scalar|null>`
dla symboli zastępczych `?`. `query()` zwraca
`list<array<string, scalar|null>>`; wartości są normalizowane do takiej samej
postaci ciągu znaków lub null przez adaptery PDO i mysqli.

---

## Snapshotter

`Merql\Snapshot\Snapshotter` przechwytuje stan bazy danych. W przeciwieństwie do fasady daje pełną kontrolę nad filtrami i nie zapisuje danych automatycznie.

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

Jeśli nie podano sterownika, jest on automatycznie wykrywany na podstawie tożsamości połączenia. Przekaż
`IdentityRuleSet`, gdy tożsamość wierszy dla poszczególnych tabel ma zastępować domyślne ustawienia schematu.

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

Przechwyć bieżący stan. Podaj nazwy tabel, aby ograniczyć zakres. Przekaż obiekty filtrów, aby wykluczyć tabele, kolumny lub wiersze.

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

Przechwytuj tabele fizyczne pod kanonicznymi nazwami tabel. Użyj tego, gdy ta sama
tabela logiczna ma różne nazwy fizyczne w poszczególnych środowiskach.

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

Wynikowa migawka przechowuje kanoniczne nazwy tabel, zachowując schemat
fizycznej tabeli, skonfigurowane kolumny tożsamości, klucze wierszy i filtry.

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

Utwórz migawkę z surowych danych bez połączenia z bazą danych. Przydatne do testów lub importowania danych z innego źródła.

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

---

## Migawka

`Merql\Snapshot\Snapshot` to obiekt wartości tylko do odczytu, reprezentujący kompletny stan bazy danych.

```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` zawiera wiersze, odciski palców i schemat jednej tabeli.

```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` to dane wejściowe dla `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` określa kolumny używane do budowania kluczy wierszy.

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

Rozpoznaj domyślną regułę tożsamości ze schematu: klucz główny, pierwszy klucz unikatowy,
a następnie zapasowo zawartość.

### `toArray(): array`

Serializuj regułę jako `['type' => string, 'columns' => list<string>]`.

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

Odtwórz regułę z zserializowanych danych.

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` przechowuje zastąpienia tożsamości dla poszczególnych tabel.

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

Zwróć nowy zestaw reguł z dodanym zastąpieniem dla jednej tabeli.

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

Zwraca wpisy `IdentityConflict`, gdy wiele wierszy dla danej reguły daje ten sam klucz.

### `toArray(): array`

Serializuj reguły tabel jako tablicę bezpieczną dla JSON.

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

Odtwórz zestaw reguł z zserializowanych danych.

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` zapisuje i wczytuje migawki jako pliki JSON.

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

Ustaw katalog przechowywania. Domyślnie jest to `.merql/snapshots`.

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

Zapisz migawkę na dysku.

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

Wczytaj migawkę według nazwy. Zgłasza `SnapshotException`, jeśli nie znaleziono pliku.

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

Sprawdź, czy plik migawki istnieje.

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

Usuń plik migawki.

---

## Differ

`Merql\Diff\Differ` porównuje dwie migawki i tworzy zestaw zmian.

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

Porównaj dwie wartości po konwersji do ciągu znaków. NULL jest traktowane jako odrębna wartość: `NULL === NULL` to prawda, `NULL === ''` to fałsz.

---

## Zestaw zmian

`Merql\Diff\Changeset` to kolekcja operacji na poziomie wierszy, tylko do odczytu.

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

Obiekty wartości tylko do odczytu reprezentujące pojedyncze zmiany wierszy.

### 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` reprezentuje pojedynczą zmianę kolumny w ramach aktualizacji wiersza.

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

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` to główny algorytm scalania.

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

Przekaż `CellMergeConfig`, aby włączyć scalanie na poziomie komórek dla kolumn TEXT i 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`

Wykonaj scalanie trójstronne. Wewnętrznie oblicza zestawy zmian (od stanu bazowego do naszej strony i od stanu bazowego do ich strony), a następnie je scala.

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

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

Skrót do scalania dwukierunkowego. Równoważne `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

Zwraca `SchemaException[]` dla wszelkich różnic schematu wykrytych podczas ostatniego scalania (kolumn dodanych lub usuniętych między migawkami).

---

## MergeResult

`Merql\Merge\MergeResult` to obiekt wartości tylko do odczytu zawierający wynik scalania.

```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` to serializowalny model przeglądu scalania.

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

Użyj `MergePlanSerializer::toJson()` i `::fromJson()`, aby zapewnić stabilną obsługę konwersji JSON w obie strony.

## Wybory

`ChangeGroupSelection` przechowuje identyfikatory przygotowanych grup zmian. Przekształć go w
`OperationSelection` podczas tworzenia wybranych wyników scalania lub planów wycofania.

```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` przechowuje operacje odwrotne i oczekiwania dotyczące rozbieżności
przy wybranym zastosowaniu zmian.

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

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

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

Użyj `RollbackDrift` przed zastosowaniem wycofania, a następnie `RollbackPlanApplier`, aby uruchomić
operacje odwrotne.

---

## MergeOperation

`Merql\Merge\MergeOperation` to pojedyncza, rozstrzygnięta operacja scalania.

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

Stałe typów:

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

---

## Konflikt

`Merql\Merge\Conflict` reprezentuje nierozwiązany konflikt scalania.

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

| Typ | Znaczenie |
|---|---|
| `update_update` | Obie strony zmieniły tę samą kolumnę na różne wartości |
| `update_delete` | Nasza strona zaktualizowała wiersz, który ich strona usunęła |
| `delete_update` | Nasza strona usunęła wiersz, który ich strona zaktualizowała |
| `insert_insert` | Obie strony wstawiły wiersz z tym samym kluczem głównym |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` stosuje zasady rozstrzygania do wszystkich konfliktów w wyniku scalania.

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

Zwraca nowy obiekt `MergeResult` z konfliktami rozwiązanymi zgodnie z przyjętą zasadą.

```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` to wyliczenie bazowe z trzema przypadkami.

```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` wykonuje logikę scalania dla poszczególnych kolumn w jednym wierszu. Jest wewnętrznie używany przez `ThreeWayMerge`.

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

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

---

## Applier

`Merql\Apply\Applier` wykonuje wynik scalania na bazie danych.

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

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

Wykonuje wszystkie operacje w ramach jednej transakcji. W razie błędu wycofuje transakcję i zgłasza go w `ApplyResult::errors()` zamiast rzucać wyjątek. Zgłasza `ConflictException`, jeśli wynik zawiera nierozwiązane konflikty.

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

Kolejność kluczy obcych jest obsługiwana automatycznie: wstawienia są sortowane według zależności, a usunięcia w odwrotnej kolejności zależności.

---

## ApplyResult

`Merql\Apply\ApplyResult` to obiekt wartości tylko do odczytu zwracany przez moduł stosujący zmiany.

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

Po wycofaniu zastosowania zmian `rowsAffected()` nadal zlicza instrukcje wykonane przed błędem ([#4](https://github.com/inline0/merql/issues/4)). Przed uznaniem tej liczby za wiarygodną sprawdź `hasErrors()`.

---

## DryRun

`Merql\Apply\DryRun` generuje czytelne dla człowieka instrukcje SQL bez wykonywania czegokolwiek.

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

Zwraca listę ciągów SQL z wstawionymi wartościami (wyłącznie do wyświetlania).

```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` generuje parametryzowane instrukcje SQL. Jest wewnętrznie używany zarówno przez `Applier`, jak i `DryRun`.

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

Zwraca `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 scalania komórek dla określonych kolumn lub typów kolumn.

### `auto(): self` (statyczna)

Fabryka ułatwiająca przypisanie `TextCellMerger` do kolumn TEXT/LONGTEXT oraz `JsonCellMerger` do kolumn JSON.

```php
use Merql\CellMerge\CellMergeConfig;

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

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

Przypisz moduł scalający do konkretnej kolumny. Użyj `table.column` dla nazw kwalifikowanych albo samego `column` dla wszystkich tabel.

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

Przypisz mechanizm scalania do wszystkich kolumn pasujących do wzorca typu (dopasowanie podciągu bez rozróżniania wielkości liter).

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

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

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

Znajdź mechanizm scalania dla podanej tabeli, kolumny i typu. Priorytet: kwalifikowana nazwa kolumny, niekwalifikowana nazwa kolumny, wzorzec typu, wartość domyślna (nieprzezroczysta).

---

## CellMerger (interfejs)

`Merql\CellMerge\CellMerger` to interfejs strategii scalania na poziomie komórek.

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

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

Dostępne są trzy wbudowane implementacje.

### TextCellMerger

`Merql\CellMerge\TextCellMerger` scala tekst wielowierszowy za pomocą trójstronnego porównania wiersz po wierszu (tego samego algorytmu, którego git używa do zawartości plików). Gdy obie strony edytują różne wiersze, scalanie przebiega bez konfliktów. Gdy obie strony różnie edytują ten sam wiersz, zgłaszany jest 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` scala wartości JSON, porównując niezależnie klucze najwyższego poziomu. Każdy klucz jest scalany z użyciem logiki trójstronnej. Zagnieżdżone obiekty są porównywane jako nieprzezroczyste ciągi JSON (bez rekurencyjnego scalania).

```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` jest wartością domyślną. Traktuje wartości jako nieprzezroczyste ciągi i zawsze zgłasza konflikt, gdy się różnią.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` to typ zwracany przez wszystkie mechanizmy scalania komórek.

```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` to reprezentacja struktury tabeli tylko do odczytu.

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

---

## System sterowników

merql używa rozszerzalnego systemu sterowników do abstrakcji operacji specyficznych dla baz danych. Sterowniki są wykrywane automatycznie na podstawie tożsamości sterownika połączenia.

### Driver (interfejs)

`Merql\Driver\Driver` określa kontrakt, który implementują wszystkie sterowniki baz danych.

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

Wbudowane implementacje: `MysqlDriver` i `SqliteDriver`.

### DriverFactory

`Merql\Driver\DriverFactory` automatycznie wykrywa sterownik na podstawie połączenia.

```php
use Merql\Driver\DriverFactory;

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

Zarejestruj niestandardowy sterownik dla innych baz danych:

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

---

## Filtry

### TableFilter

`Merql\Filter\TableFilter` uwzględnia tabele lub je wyklucza za pomocą wzorców glob.

```php
use Merql\Filter\TableFilter;

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

### ColumnFilter

`Merql\Filter\ColumnFilter` usuwa określone kolumny ze zrzutów.

```php
use Merql\Filter\ColumnFilter;

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

### RowFilter

`Merql\Filter\RowFilter` filtruje wiersze za pomocą funkcji predykatu.

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

---

## Wyjątki

### SnapshotException

`Merql\Exceptions\SnapshotException` jest zgłaszany, gdy nie można znaleźć zrzutu lub ma on nieprawidłową nazwę.

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

### MergeException

`Merql\Exceptions\MergeException` jest zgłaszany, gdy do scalania brakuje wymaganego zrzutu.

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

### ConflictException

`Merql\Exceptions\ConflictException` jest zgłaszany przy próbie zastosowania wyniku scalania z nierozwiązanymi konfliktami.

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

### SchemaException

`Merql\Exceptions\SchemaException` jest zgłaszany (lub gromadzony), gdy schematy różnią się między zrzutami.

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

Niezgodności schematów nie zatrzymują scalania. Są gromadzone i dostępne do wglądu za pomocą `$result->schemaMismatches()`.
