
# PHP API-viite

Täydellinen viite kaikista merql:n julkisista luokista ja metodeista.

## Merql (julkisivu)

`Merql\Merql` on tavallisten toimintojen staattinen aloituspiste. Se käärii alemman tason luokat ja hallitsee tilannevedosten säilyttämisen automaattisesti.

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

Alusta facade tietokantayhteydellä. Tämä on tehtävä ennen toimintoja `snapshot()`, `apply()` tai `applyGuarded()`. Kutsu sitä uudelleen vaihtaaksesi tietokantaa, esimerkiksi ottaaksesi tilannevedokset meidän ja heidän tietokannoistaan kahdesta eri kopiosta. Ilman `$driver`-määritystä se kutsuu toimintoa `DriverFactory::create()`, joka aiheuttaa poikkeuksen muilla moottoreilla kuin MySQL ja SQLite, ellei ajuria ole rekisteröity. Ohita automaattinen tunnistus välittämällä ajuri tai ohita taulukkokohtainen rivitunniste tilannevedosta otettaessa välittämällä identiteettisääntöjoukko.

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

Tallenna tietokannan nykyinen tila levylle. Anna taulukoiden nimistä koostuva taulukko, jos haluat ottaa tilannevedoksen tietyistä taulukoista, tai jätä se pois, jos haluat ottaa tilannevedoksen kaikista taulukoista.

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

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

Lataa kaksi nimettyä tilannevedosta ja laske niiden välinen muutosjoukko. `init()`-määritystä ei tarvita, koska toiminto käyttää tallennettuja tilannevedoksia.

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

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

Kolmen nimetyn tilannevedoksen kolmisuuntainen yhdistäminen. Palauttaa tuloksen, joka sisältää puhtaat toiminnot ja mahdolliset ristiriidat. Yhdistää saraketasolla: facade muodostaa `ThreeWayMerge` ilman `CellMergeConfig`-määritystä, joten solutason yhdistämistä ei tehdä. Käytä siihen suoraan toimintoa [`ThreeWayMerge`](#threewaymerge).

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

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

Kaksisuuntainen yhdistäminen: sovella muutokset pohjaan. Vastaa toimintoa `merge($base, $base, $changes)`. Tuottaa aina puhtaan tuloksen, koska samanaikaisia muokkauksia ei ole. Vain saraketasolla, kuten toiminnossa `merge()`.

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

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

Suorita puhdas yhdistämistulos tietokannassa, joka välitettiin toiminnolle `init()`. Edellyttää `init()`-määritystä. Aiheuttaa poikkeuksen `ConflictException`, jos ratkaisemattomia ristiriitoja on jäljellä.

Tuloksen jokainen toiminto suoritetaan, joten toiminnon `merge()` koko tulos soveltuu puhtaasti vain tietokantaan, jossa on yhä pohjan tila. Jos tietokannassa on jo toisen puolen tila, sovella valikoituja toimintoja, jotka siitä puuttuvat; katso [Guarded Apply](https://merql.inline0.dev/fi/docs/apply/guarded).

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

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

Luo kolmesta nimetystä tilannevedoksesta yhdistämissuunnitelma, joka on valmis UI-toimintoa varten. Suunnitelma sisältää vakaat toimintotunnukset, muutosryhmien tunnukset, toimintojen kontekstin, ristiriidat, skeemavirheet, yhteenvedon, metatiedot ja suunnitelman tiivisteen. Yhdistäminen tehdään saraketasolla, kuten toiminnossa `merge()`; välitä solutasolla yhdistetty tulos toiminnolle `MergePlanBuilder::build()`, jos haluat yhdistää solutasolla.

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

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

Sovella puhdas yhdistämistulos optimistisin reaaliaikaisen rivin esiehdoin. Päivityksissä ja poistoissa nykyisten reaaliaikaisten rivien on vastattava arvoja `$expectedLive`; lisäyksissä kohdetunniste ei saa olla jo olemassa. `$expectedLive` on kohdetietokannan tilannevedos (heidän tietokantansa, kun meidän tietokantamme viedään siihen tietokantaan, josta heidän tilannevedoksensa otettiin). Mikä tahansa lause, joka vaikuttaa nollaan riviin, peruuttaa tapahtuman.

```php
use Merql\Snapshot\SnapshotStore;

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

### `reset(): void`

Tyhjennä sisäiset yhteys- ja Snapshotter-instanssit. Käytetään pääasiassa testeissä tilan nollaamiseen suoritusten välillä.

---

## Yhteys

`Merql\Connection` luo yhteyssovittimet järkevillä oletusarvoilla. PDO-yhteyksissä virheet aiheuttavat poikkeuksia, hakutilana on assosiatiivinen taulukko, emuloidut valmistelut on poistettu käytöstä ja haetut arvot muunnetaan merkkijonoiksi. mysqli-sovitin normalisoi haetut arvot samaan merkkijono- tai null-muotoon.

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

Luo SQLite-yhteys. Ottaa vierasavaimet automaattisesti käyttöön toiminnolla `PRAGMA foreign_keys = ON`.

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

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

Muodosta yhteys millä tahansa PDO:n DSN-merkkijonolla. merql sisältää ajurit vain kohteille `mysql` ja `sqlite`. Muut DSN:t avaavat yhteyden, mutta toiminnot `Merql::init()` ja `Snapshotter` sekä soveltajat aiheuttavat poikkeuksen `Unsupported database driver`, ellei ajuria rekisteröidä toiminnolla `DriverFactory::register()`.

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

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

Kääri olemassa oleva PDO-instanssi.

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

Kääri olemassa oleva mysqli-instanssi.

### Sovittimen sopimus

Kaikki sovittimet toteuttavat rajapinnan `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;
}
```

Toiminnot `execute()`, `query()` ja `scalar()` hyväksyvät sijaintiin perustuvat `list<scalar|null>`-parametrit paikkamerkeille `?`. `query()` palauttaa arvon `list<array<string, scalar|null>>`. Arvot normalisoidaan samaan merkkijono- tai null-muotoon PDO- ja mysqli-sovittimissa.

---

## Snapshotter

`Merql\Snapshot\Snapshotter` tallentaa tietokannan tilan. Facadesta poiketen se antaa sinun hallita suodattimia täysin eikä tallenna automaattisesti.

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

Ajuri tunnistetaan automaattisesti yhteyden identiteetistä, jos sitä ei anneta. Välitä `IdentityRuleSet`, kun taulukkokohtaisen rivitunnisteen tulee ohittaa skeeman oletusarvot.

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

Tallenna nykyinen tila. Rajaa käsittelyä välittämällä taulukoiden nimet. Sulje taulukoita, sarakkeita tai rivejä pois suodatinobjekteilla.

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

Tallenna fyysiset taulukot kanonisilla taulukkonimillä. Käytä tätä, kun samalla loogisella taulukolla on eri fyysiset nimet eri ympäristöissä.

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

Tuloksena oleva tilannevedos tallentaa kanoniset taulukkonimet, mutta säilyttää fyysisen taulukkoskeeman, määritetyt identiteettisarakkeet, rivien avaimet ja suodattimet.

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

Muodosta tilannevedos raakadatasta ilman tietokantayhteyttä. Hyödyllinen testauksessa tai tuotaessa tietoja toisesta lähteestä.

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

---

## Tilannevedos

`Merql\Snapshot\Snapshot` on vain luku -arvo-olio, joka esittää tietokannan täydellistä tilaa.

```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` sisältää yhden taulukon rivit, tiivisteet ja skeeman.

```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` on toiminnon `Snapshotter::fromData()` syötedataa.

```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` määrittää rivien avainten muodostamisessa käytettävät sarakkeet.

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

Ratkaise skeeman perusteella oletusarvoinen identiteettisääntö: ensisijainen avain, ensimmäinen uniikki avain ja lopuksi sisältöön perustuva varmistus.

### `toArray(): array`

Sarjallista sääntö muodossa `['type' => string, 'columns' => list<string>]`.

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

Luo sääntö sarjallistetusta datasta.

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` tallentaa taulukkokohtaiset identiteettiohitukset.

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

Palauta uusi sääntöjoukko, johon on lisätty yhden taulukon ohitus.

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

Palauta arvon `IdentityConflict` merkinnät, kun useat rivit tuottavat samalle säännölle saman avaimen.

### `toArray(): array`

Sarjallista taulukkosäännöt arvon JSON kanssa yhteensopivaksi taulukoksi.

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

Luo sääntöjoukko sarjallistetusta datasta.

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` tallentaa ja lataa tilannevedokset JSON-tiedostoina.

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

Aseta tallennushakemisto. Oletus on `.merql/snapshots`.

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

Kirjoita tilannevedos levylle.

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

Lataa tilannevedos nimellä. Aiheuttaa poikkeuksen `SnapshotException`, jos sitä ei löydy.

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

Tarkista, onko tilannevedostiedosto olemassa.

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

Poista tilannevedostiedosto.

---

## Erottelija

`Merql\Diff\Differ` vertaa kahta tilannevedosta ja tuottaa muutosjoukon.

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

Vertaa kahta arvoa merkkijonoksi muuntamisen jälkeen. NULL käsitellään erillisenä arvona: `NULL === NULL` on tosi, `NULL === ''` on epätosi.

---

## Muutosjoukko

`Merql\Diff\Changeset` on vain luku -kokoelma rivitason toimintoja.

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

Vain luku -arvo-oliot, jotka esittävät yksittäisiä rivimuutoksia.

### 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` esittää yhtä sarakemuutosta rivin päivityksessä.

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

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` on yhdistämisen ydinalgoritmi.

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

Välitä `CellMergeConfig`, jos haluat ottaa käyttöön solutason yhdistämisen TEXT- ja JSON-sarakkeissa.

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

Suorita kolmisuuntainen yhdistäminen. Laskee muutosjoukot sisäisesti (pohjasta meidän versioon ja pohjasta heidän versioonsa) ja yhdistää ne.

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

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

Kaksisuuntaisen yhdistämisen pikatoiminto. Vastaa toimintoa `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

Palauttaa arvon `SchemaException[]` kaikista skeemaeroista, jotka havaittiin viimeisimmässä yhdistämisessä (tilannevedosten välillä lisätyistä tai poistetuista sarakkeista).

---

## MergeResult

`Merql\Merge\MergeResult` on vain luku -arvo-olio, joka sisältää yhdistämisen tuloksen.

```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` on sarjallistettava yhdistämisen tarkastelumalli.

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

Käytä toimintoja `MergePlanSerializer::toJson()` ja `::fromJson()` vakaissa JSON-edestakaisissa muunnoksissa.

## Valinnat

`ChangeGroupSelection` tallentaa vaiheistettujen muutosryhmien tunnukset. Muunna se arvoksi `OperationSelection`, kun muodostat valittuja yhdistämistuloksia tai palautussuunnitelmia.

```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` tallentaa käänteiset toiminnot ja valittuun soveltamiseen liittyvät odotukset poikkeamien varalta.

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

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

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

Käytä toimintoa `RollbackDrift` ennen palautuksen soveltamista ja suorita sitten käänteiset toiminnot toiminnolla `RollbackPlanApplier`.

---

## MergeOperation

`Merql\Merge\MergeOperation` on yksi ratkaistu yhdistämistoiminto.

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

Tyyppivakiot:

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

---

## Ristiriita

`Merql\Merge\Conflict` esittää ratkaisematta jäänyttä yhdistämisristiriitaa.

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

### Ristiriitatyypit

| Tyyppi | Merkitys |
|---|---|
| `update_update` | Molemmat puolet muuttivat saman sarakkeen eri arvoon |
| `update_delete` | Meidän puolemme päivitti rivin, jonka heidän puolensa poisti |
| `delete_update` | Meidän puolemme poisti rivin, jonka heidän puolensa päivitti |
| `insert_insert` | Molemmat puolet lisäsivät rivin, jolla on sama ensisijainen avain |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` soveltaa ratkaisukäytäntöä yhdistämistuloksen kaikkiin ristiriitoihin.

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

Palauttaa uuden arvon `MergeResult`, jonka ristiriidat on ratkaistu käytännön mukaisesti.

```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` on backed enum, jossa on kolme tapausta.

```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` suorittaa yhden rivin sarakekohtaisen yhdistämislogiikan. Käytetään sisäisesti toiminnossa `ThreeWayMerge`.

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

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

---

## Soveltaja

`Merql\Apply\Applier` suorittaa yhdistämistuloksen tietokannassa.

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

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

Suorittaa kaikki toiminnot yhdessä tapahtumassa. Peruuttaa tapahtuman virheen sattuessa ja ilmoittaa siitä arvossa `ApplyResult::errors()` poikkeuksen aiheuttamisen sijaan. Aiheuttaa poikkeuksen `ConflictException`, jos tuloksessa on ratkaisemattomia ristiriitoja.

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

Vierasavainten järjestys hoidetaan automaattisesti: lisäykset lajitellaan riippuvuusjärjestykseen ja poistot päinvastaiseen järjestykseen.

---

## ApplyResult

`Merql\Apply\ApplyResult` on vain luku -arvo-olio, jonka soveltaja palauttaa.

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

Peruutetun soveltamisen jälkeen `rowsAffected()` laskee edelleen ennen virhettä suoritetut lauseet ([#4](https://github.com/inline0/merql/issues/4)). Tarkista `hasErrors()` ennen kuin luotat lukumäärään.

---

## DryRun

`Merql\Apply\DryRun` muodostaa ihmisluettavia SQL-lauseita suorittamatta mitään.

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

Palauttaa luettelon SQL-merkkijonoista, joissa arvot on sijoitettu suoraan (vain näyttötarkoituksiin).

```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` muodostaa parametroidut SQL-lauseet. Sekä `Applier` että `DryRun` käyttävät sitä sisäisesti.

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

Palauttaa arvon `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` määrittää solutason yhdistämisstrategiat tietyille sarakkeille tai saraketyypeille.

### `auto(): self` (staattinen)

Helppokäyttöinen tehdas, joka määrittää `TextCellMerger`-toiminnon TEXT/LONGTEXT-sarakkeille ja `JsonCellMerger`-toiminnon sarakkeille, joiden tyyppi on JSON.

```php
use Merql\CellMerge\CellMergeConfig;

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

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

Määritä yhdistin tietylle sarakkeelle. Käytä toimintoa `table.column`, jos nimet on määritelty taulukoineen, tai pelkkää nimeä `column`, jos haluat kattaa kaikki taulukot.

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

Määritä yhdistin kaikille sarakkeille, joiden tyyppi vastaa tyyppikuviota (kirjainkoosta riippumaton osamerkkijonohaku).

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

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

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

Hae yhdistin annetulle taulukolle, sarakkeelle ja tyypille. Tärkeysjärjestys: taulukon sisältävä sarakenimi, taulukkoon sitomaton sarakenimi, tyyppikuvio, oletus (opaakki).

---

## CellMerger (rajapinta)

`Merql\CellMerge\CellMerger` on solutason yhdistämisstrategioiden rajapinta.

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

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

Mukana on kolme sisäänrakennettua toteutusta.

### TextCellMerger

`Merql\CellMerge\TextCellMerger` yhdistää monirivistä tekstiä käyttämällä kolmisuuntaista rivi riviltä tehtävää vertailua (samaa algoritmia, jota git käyttää tiedostosisällölle). Jos molemmat puolet muokkaavat eri rivejä, yhdistäminen onnistuu ilman ristiriitoja. Jos molemmat puolet muokkaavat samaa riviä eri tavoin, toiminto ilmoittaa ristiriidasta.

```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` yhdistää arvoja JSON vertaamalla ylimmän tason avaimia toisistaan riippumatta. Kukin avain yhdistetään kolmisuuntaisella logiikalla. Sisäkkäisiä olioita verrataan opaakkeina JSON-merkkijonoina (niitä ei yhdistetä rekursiivisesti).

```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` on oletusyhdistin. Se käsittelee arvot opaakkeina merkkijonoina ja ilmoittaa aina ristiriidasta, kun ne eroavat.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` on kaikkien soluyhdistimien palautustyyppi.

```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` on vain luku -muotoinen esitys taulukon rakenteesta.

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

---

## Ajurijärjestelmä

merql käyttää liitettävää ajurijärjestelmää tietokantakohtaisten toimintojen abstrahointiin. Ajurit tunnistetaan automaattisesti yhteyden ajuritunnisteen perusteella.

### Driver (rajapinta)

`Merql\Driver\Driver` määrittää sopimuksen, jota kaikkien tietokanta-ajurien on noudatettava.

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

Sisäänrakennetut toteutukset: `MysqlDriver` ja `SqliteDriver`.

### DriverFactory

`Merql\Driver\DriverFactory` tunnistaa ajurin automaattisesti yhteydestä.

```php
use Merql\Driver\DriverFactory;

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

Rekisteröi mukautettu ajuri muille tietokannoille:

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

---

## Suodattimet

### TableFilter

`Merql\Filter\TableFilter` sisällyttää tai sulkee pois taulukoita glob-kuvioiden avulla.

```php
use Merql\Filter\TableFilter;

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

### ColumnFilter

`Merql\Filter\ColumnFilter` poistaa tietyt sarakkeet tilannevedoksista.

```php
use Merql\Filter\ColumnFilter;

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

### RowFilter

`Merql\Filter\RowFilter` suodattaa rivejä predikaattifunktion avulla.

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

---

## Poikkeukset

### SnapshotException

`Merql\Exceptions\SnapshotException` heitetään, kun tilannevedosta ei löydy tai sen nimi on virheellinen.

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

### MergeException

`Merql\Exceptions\MergeException` heitetään, kun yhdistäminen edellyttää tilannevedosta, joka puuttuu.

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

### ConflictException

`Merql\Exceptions\ConflictException` heitetään, kun yritetään soveltaa yhdistämistulosta, jossa on ratkaisemattomia ristiriitoja.

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

### SchemaException

`Merql\Exceptions\SchemaException` heitetään (tai kerätään), kun tilannevedosten skeemat eroavat toisistaan.

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

Skeemavirheet eivät pysäytä yhdistämistä. Ne kerätään, ja niitä voi tarkastella arvon `$result->schemaMismatches()` kautta.
