
# Referenca PHP API

Celotna referenca za vse javne razrede in metode v merql.

## Merql (fasada)

`Merql\Merql` je statična vstopna točka za običajna opravila. Ovije nižje ravni razredov in samodejno upravlja shranjevanje posnetkov.

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

Inicializirajte fasado s povezavo do zbirke podatkov. Poklicati jo je treba pred
`snapshot()`, `apply()` ali `applyGuarded()`; pokličite jo znova za preklop med zbirkami podatkov,
na primer za ustvarjanje posnetkov naše in njihove kopije iz dveh različnih kopij. Brez
`$driver` pokliče `DriverFactory::create()`, ki sproži izjemo pri mehanizmih, ki niso MySQL ali SQLite, razen če je registriran gonilnik. Podajte gonilnik, da preglasite samodejno zaznavanje, ali nabor pravil identitete, da preglasite identiteto vrstic po tabelah med zajemom posnetka.

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

Zajamite trenutno stanje zbirke podatkov in ga shranite na disk. Podajte polje imen tabel za ustvarjanje posnetkov iz določenih tabel ali ga izpustite za vse tabele.

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

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

Naložite dva poimenovana posnetka in izračunajte nabor sprememb med njima. `init()` ne potrebujete, saj deluje s shranjenimi posnetki.

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

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

Tristransko združevanje treh poimenovanih posnetkov. Vrne rezultat s čistimi operacijami in morebitnimi spori. Združuje na ravni stolpcev: fasada sestavi `ThreeWayMerge` brez `CellMergeConfig`, zato se združevanje na ravni celic ne izvede. Za to neposredno uporabite [`ThreeWayMerge`](#threewaymerge).

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

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

Dvosmerno združevanje: uveljavitev sprememb na osnovnem stanju. Enakovredno je `merge($base, $base, $changes)`. Vedno ustvari čist rezultat, ker ni sočasnih urejanj. Samo na ravni stolpcev, tako kot `merge()`.

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

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

Izvedite čist rezultat združevanja v zbirki podatkov, podani v `init()`. Zahteva `init()`. Če ostanejo nerešeni spori, sproži `ConflictException`.

Izvede se vsaka operacija v rezultatu, zato je mogoče celoten rezultat `merge()` čisto uveljaviti samo v zbirki podatkov, ki še vedno vsebuje osnovno stanje. Če zbirka podatkov že vsebuje eno od strani, uveljavite izbrane operacije, ki ji manjkajo; glejte [Varovano uveljavljanje](https://merql.inline0.dev/sl/docs/apply/guarded).

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

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

Iz treh poimenovanih posnetkov sestavite načrt združevanja, pripravljen za UI. Načrt vključuje stabilne ID-je operacij, ID-je skupin sprememb, kontekst operacij, spore, neskladja shem, povzetek, metapodatke in zgoščeno vrednost načrta. Združevanje je na ravni stolpcev, kot pri `merge()`; za združevanje na ravni celic posredujte rezultat z združenimi celicami v `MergePlanBuilder::build()`.

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

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

Uveljavite čist rezultat združevanja z optimističnimi predpogoji za aktivne vrstice. Pri posodobitvah in izbrisih se morajo trenutne aktivne vrstice ujemati z `$expectedLive`; pri vstavljanju identiteta cilja še ne sme obstajati. `$expectedLive` je posnetek ciljne zbirke podatkov (njihove kopije, kadar našo kopijo uveljavljate v zbirki podatkov, iz katere je bila ustvarjena njihova kopija). Vsak stavek, ki vpliva na nič vrstic, povrne transakcijo.

```php
use Merql\Snapshot\SnapshotStore;

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

### `reset(): void`

Počistite interne primerke povezave in Snapshotter. Uporablja se predvsem v testih za ponastavitev stanja med zagoni.

---

## Povezava

`Merql\Connection` zgradi prilagodilnike povezav z ustreznimi privzetimi nastavitvami. Povezave PDO ob napakah sprožijo izjeme, uporabljajo asociativni način pridobivanja, brez emuliranih pripravljenih stavkov in pretvorijo pridobljene vrednosti v nize. Prilagodilnik mysqli normalizira pridobljene vrednosti v enako obliko niz ali 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`

Ustvari povezavo SQLite. Samodejno omogoči tuje ključe prek `PRAGMA foreign_keys = ON`.

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

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

Povežite se s katerim koli nizom DSN za PDO. merql vključuje gonilnike samo za `mysql` in `sqlite`; druge vrednosti DSN vzpostavijo povezavo, vendar `Merql::init()`, `Snapshotter` in orodja za uveljavitev sprožijo `Unsupported database driver`, razen če je gonilnik registriran z `DriverFactory::register()`.

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

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

Ovijte obstoječi primerek PDO.

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

Ovijte obstoječi primerek mysqli.

### Pogodba prilagodilnika

Vsi prilagodilniki izvajajo `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()` in `scalar()` sprejemajo pozicijske parametre `list<scalar|null>`
za nadomestna mesta `?`. `query()` vrne
`list<array<string, scalar|null>>`; vrednosti so v prilagodilnikih PDO in mysqli normalizirane v enako
obliko niz ali null.

---

## Snapshotter

`Merql\Snapshot\Snapshotter` zajame stanje zbirke podatkov. Za razliko od fasade vam omogoča popoln nadzor nad filtri in posnetkov ne shrani samodejno.

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

Gonilnik se samodejno zazna iz identitete povezave, če ga ne podate. Podajte
`IdentityRuleSet`, kadar naj identiteta vrstic, določena za posamezno tabelo, preglasi privzete nastavitve sheme.

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

Zajame trenutno stanje. Podajte imena tabel, da omejite obseg. Podajte predmete filtrov za izključitev tabel, stolpcev ali vrstic.

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

Zajame fizične tabele pod kanoničnimi imeni tabel. Uporabite, kadar ima ista
logična tabela v različnih okoljih različna fizična imena.

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

Nastali posnetek shrani kanonična imena tabel, hkrati pa ohrani shemo fizičnih tabel,
konfigurirane stolpce identitete, ključe vrstic in filtre.

### `fromData(string $name, array $tableData): Snapshot` (statično)

Ustvari posnetek iz neobdelanih podatkov brez povezave do zbirke podatkov. Uporabno za testiranje ali uvoz podatkov iz drugega vira.

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

---

## Posnetek

`Merql\Snapshot\Snapshot` je nespremenljiv objekt vrednosti, ki predstavlja celotno stanje zbirke podatkov.

```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` vsebuje vrstice ene tabele, njihove prstne odtise in shemo.

```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` so vhodni podatki za `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` določa stolpce, uporabljene za tvorjenje ključev vrstic.

```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` (statično)

Iz sheme razreši privzeto pravilo identitete: primarni ključ, prvi enolični ključ,
nato nadomestna možnost na podlagi vsebine.

### `toArray(): array`

Pravilo serializira kot `['type' => string, 'columns' => list<string>]`.

### `fromArray(array $data): IdentityRule` (statično)

Iz serializiranih podatkov ustvari pravilo.

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` shranjuje preglasitve identitete za posamezne tabele.

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

Vrne nov nabor pravil z dodano preglasitvijo za eno tabelo.

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

Vrne vnose `IdentityConflict`, kadar več vrstic za isto pravilo ustvari isti ključ.

### `toArray(): array`

Pravila tabel serializira kot polje, varno za JSON.

### `fromArray(array $data): IdentityRuleSet` (statično)

Iz serializiranih podatkov ustvari nabor pravil.

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` shranjuje in nalaga posnetke kot datoteke JSON.

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

Nastavi imenik za shranjevanje. Privzeto je `.merql/snapshots`.

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

Zapiše posnetek na disk.

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

Naloži posnetek po imenu. Če ga ne najde, sproži `SnapshotException`.

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

Preveri, ali datoteka posnetka obstaja.

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

Odstrani datoteko posnetka.

---

## Razlikovanje

`Merql\Diff\Differ` primerja dva posnetka in ustvari nabor sprememb.

### `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` (statično)

Primerja dve vrednosti z njuno pretvorbo v niza. NULL se obravnava kot ločena vrednost: `NULL === NULL` je true, `NULL === ''` je false.

---

## Nabor sprememb

`Merql\Diff\Changeset` je zbirka operacij na ravni vrstic, samo za branje.

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

Objekti vrednosti, samo za branje, ki predstavljajo posamezne spremembe vrstic.

### 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` predstavlja spremembo enega stolpca v posodobitvi vrstice.

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

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` je osrednji algoritem za združevanje.

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

Posredujte `CellMergeConfig`, da omogočite združevanje na ravni celic za stolpca TEXT in 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`

Izvede trosmerno združevanje. Nize sprememb izračuna interno (od osnove do naših sprememb in od osnove do njihovih sprememb) ter jih združi.

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

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

Bližnjica za dvosmerno združevanje. Enakovredno `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

Vrne `SchemaException[]` za vse razlike v shemi, zaznane med zadnjim združevanjem (stolpci, dodani ali odstranjeni med posnetki).

---

## MergeResult

`Merql\Merge\MergeResult` je objekt vrednosti, samo za branje, ki vsebuje rezultat združevanja.

```php
$result->isClean();                // bool: no conflicts
$result->operations();             // list<MergeOperation>
$result->conflicts();              // list<Conflict>
$result->operationCount();         // int
$result->conflictCount();          // int
$result->baseSnapshot();           // ?Snapshot
$result->schemaMismatches();       // list<SchemaException>
$result->hasSchemaMismatches();    // bool
```

---

## MergePlan

`Merql\Plan\MergePlan` je serializabilen model za pregled združevanja.

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

Uporabite `MergePlanSerializer::toJson()` in `::fromJson()` za stabilne JSON
povratne pretvorbe.

## Izbori

`ChangeGroupSelection` hrani ID-je skupin pripravljenih sprememb. Pri gradnji izbranih rezultatov združevanja ali načrtov za povrnitev ga pretvorite v
`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` hrani inverzne operacije in pričakovanja glede odmikov
za izbrano uporabo.

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

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

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

Pred uporabo povrnitve uporabite `RollbackDrift`, nato pa `RollbackPlanApplier` za izvedbo
inverznih operacij.

---

## MergeOperation

`Merql\Merge\MergeOperation` je ena razrešena operacija iz združevanja.

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

Konstante tipov:

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

---

## Konflikt

`Merql\Merge\Conflict` predstavlja nerazrešen konflikt pri združevanju.

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

### Vrste konfliktov

| Vrsta | Pomen |
|---|---|
| `update_update` | Obe strani sta isti stolpec spremenili na različni vrednosti |
| `update_delete` | Naša stran je posodobila vrstico, ki jo je njihova stran izbrisala |
| `delete_update` | Naša stran je izbrisala vrstico, ki jo je njihova stran posodobila |
| `insert_insert` | Obe strani sta vstavili vrstico z istim primarnim ključem |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` uporabi pravilnik za razreševanje vseh konfliktov v rezultatu združevanja.

### `resolve(MergeResult $result, ConflictPolicy $policy): MergeResult` (statično)

Vrne nov `MergeResult` s konflikti, razrešenimi v skladu s pravilnikom.

```php
use Merql\Merge\ConflictResolver;
use Merql\Merge\ConflictPolicy;

// Accept all "theirs" values when conflicts arise.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

// Accept all "ours" values.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::OursWins);

// Return unchanged (for manual inspection).
$resolved = ConflictResolver::resolve($result, ConflictPolicy::Manual);
```

---

## ConflictPolicy

`Merql\Merge\ConflictPolicy` je enum z osnovnim tipom in tremi primeri.

```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` izvede logiko združevanja po stolpcih za posamezno vrstico. Interno ga uporablja `ThreeWayMerge`.

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

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

---

## Uporabnik sprememb

`Merql\Apply\Applier` izvede rezultat združevanja nad zbirko podatkov.

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

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

Vse operacije izvede v eni transakciji. Ob vsaki napaki izvede povrnitev in jo sporoči v `ApplyResult::errors()`, namesto da bi sprožil izjemo. Če rezultat vsebuje nerazrešene konflikte, sproži `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());
}
```

Razvrščanje glede na tuje ključe poteka samodejno: vstavljanja se uredijo po vrstnem redu odvisnosti, brisanja pa v obratnem vrstnem redu odvisnosti.

---

## ApplyResult

`Merql\Apply\ApplyResult` je objekt vrednosti, samo za branje, ki ga vrne uporabnik sprememb.

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

Po povrnjeni uporabi `rowsAffected()` še vedno šteje ukaze, izvedene pred napako ([#4](https://github.com/inline0/merql/issues/4)). Preden zaupate temu številu, preverite `hasErrors()`.

---

## DryRun

`Merql\Apply\DryRun` ustvari berljive SQL, ne da bi karkoli izvedel.

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

Vrne seznam nizov SQL z vstavljenimi vrednostmi (samo za prikaz).

```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` ustvari parametrizirane stavke SQL. Interno ju uporabljata tako `Applier` kot `DryRun`.

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

Vrne `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` nastavi strategije združevanja na ravni celic za določene stolpce ali vrste stolpcev.

### `auto(): self` (statično)

Tovarniška metoda za priročno dodelitev `TextCellMerger` stolpcem TEXT/LONGTEXT in `JsonCellMerger` stolpcem JSON.

```php
use Merql\CellMerge\CellMergeConfig;

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

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

Dodelite združevalnik določenemu stolpcu. Za imena s kvalifikatorjem uporabite `table.column`, za vse tabele pa samo `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`

Dodelite združevalnik vsem stolpcem, ki ustrezajo vzorcu vrste (ujemanje podniza brez upoštevanja velikosti črk).

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

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

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

Poišče združevalnik za dano tabelo, stolpec in vrsto. Prednostni vrstni red: kvalificirano ime stolpca, nekvalificirano ime stolpca, vzorec vrste, privzeto (neprosojno).

---

## CellMerger (vmesnik)

`Merql\CellMerge\CellMerger` je vmesnik za strategije združevanja na ravni celic.

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

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

Na voljo so tri vgrajene implementacije.

### TextCellMerger

`Merql\CellMerge\TextCellMerger` združuje večvrstično besedilo s trosmerno primerjavo vrstico za vrstico (isti algoritem, kot ga git uporablja za vsebino datotek). Če obe strani urejata različne vrstice, je združevanje čisto. Če obe strani isto vrstico spremenita različno, prijavi 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` združuje vrednosti JSON s samostojno primerjavo ključev najvišje ravni. Vsak ključ se združi po trosmerni logiki. Ugnezdeni objekti se obravnavajo kot neprosojni nizi JSON (ne združujejo se rekurzivno).

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
$result = $merger->merge(
    '{"a": 1, "b": 2}',
    '{"a": 10, "b": 2}',        // changed "a"
    '{"a": 1, "b": 20}',         // changed "b"
);
// $result->clean === true
// $result->value === '{"a":10,"b":20}'
```

### OpaqueCellMerger

`Merql\CellMerge\OpaqueCellMerger` je privzeti. Vrednosti obravnava kot neprosojne nize in ob njihovi različnosti vedno prijavi konflikt.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` je vrsta vrnjene vrednosti vseh združevalnikov celic.

```php
$result->clean;                    // bool: true if merge succeeded
$result->value;                    // mixed: merged value (or ours on conflict)
$result->conflicts;                // int: number of conflicts

// Factory methods.
CellMergeResult::resolved($value);           // clean merge
CellMergeResult::conflict($oursValue, $n);   // conflict with count
```

---

## TableSchema

`Merql\Schema\TableSchema` je predstavitev strukture tabele, samo za branje.

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

---

## Sistem gonilnikov

merql uporablja vtični sistem gonilnikov za abstrakcijo operacij, specifičnih za zbirko podatkov. Gonilniki se samodejno zaznajo na podlagi identitete gonilnika povezave.

### Driver (vmesnik)

`Merql\Driver\Driver` določa pogodbo, ki jo implementirajo vsi gonilniki zbirk podatkov.

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

Vgrajeni implementaciji: `MysqlDriver` in `SqliteDriver`.

### DriverFactory

`Merql\Driver\DriverFactory` samodejno zazna gonilnik iz povezave.

```php
use Merql\Driver\DriverFactory;

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

Registrirajte gonilnik po meri za druge zbirke podatkov:

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

---

## Filtri

### TableFilter

`Merql\Filter\TableFilter` vključuje ali izključuje tabele z vzorci glob.

```php
use Merql\Filter\TableFilter;

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

### ColumnFilter

`Merql\Filter\ColumnFilter` odstrani določene stolpce iz posnetkov.

```php
use Merql\Filter\ColumnFilter;

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

### RowFilter

`Merql\Filter\RowFilter` filtrira vrstice s funkcijo predikata.

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

---

## Izjeme

### SnapshotException

`Merql\Exceptions\SnapshotException` se sproži, kadar posnetka ni mogoče najti ali ima neveljavno ime.

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

### MergeException

`Merql\Exceptions\MergeException` se sproži, kadar za združevanje manjka zahtevan posnetek.

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

### ConflictException

`Merql\Exceptions\ConflictException` se sproži pri poskusu uporabe rezultata združevanja, ki vsebuje nerazrešene konflikte.

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

### SchemaException

`Merql\Exceptions\SchemaException` se sproži (ali zbere), kadar se shemi med posnetki razlikujeta.

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

Neujemanja shem ne ustavijo združevanja. Zberejo se in so za pregled na voljo prek `$result->schemaMismatches()`.
