
# Darba sākšana

Šajā pamācībā aplūkota instalēšana, savienojuma izveide ar datubāzi, momentuzņēmumu uzņemšana, atšķirību aprēķināšana un trīspusējas apvienošanas veikšana.

## Instalēšana

```bash
composer require merql/merql
```

merql nepieciešams PHP 8.2+ ar `ext-mbstring` un `ext-zlib` (abas vajadzīgas
`pitmaster/pitmaster` atkarībai). Jums nepieciešams arī paplašinājums izmantotajam
adapterim: `ext-pdo` ar `ext-pdo_sqlite` vai `ext-pdo_mysql` PDO adapterim vai `ext-mysqli` mysqli adapterim. Composer tās tikai iesaka,
tāpēc pārbaudiet, vai vajadzīgais paplašinājums ir instalēts.

Pakotne instalē CLI kā `vendor/bin/merql`. Izmantojiet merql versiju 0.4.1 vai jaunāku, lai iegūtu CLI; iepriekšējās versijās binārais fails nevarēja atrast autoloader, kad merql bija instalēts kā atkarība.

## Savienojuma izveide ar datubāzi

merql darbojas, izmantojot nelielu savienojuma adaptera saskarni. Iebūvētie adapteri ir pieejami PDO un mysqli, un iebūvētie dialektu draiveri — MySQL un SQLite.

```php
use Merql\Connection;

// SQLite (in-memory or file-based, through PDO).
$connection = Connection::sqlite(':memory:');
$connection = Connection::sqlite('/path/to/database.sqlite');

// MySQL through PDO.
$connection = Connection::mysql('127.0.0.1', 'my_database', 'root', 'password');

// MySQL through mysqli.
$connection = Connection::mysqli('127.0.0.1', 'my_database', 'root', 'password');

// Any MySQL or SQLite PDO DSN.
$connection = Connection::fromDsn('mysql:host=127.0.0.1;dbname=my_database;charset=utf8mb4', 'root', 'password');
```

`fromDsn()` atver jebkuru PDO atbalstītu DSN, taču merql ietver dialektu draiverus tikai MySQL un SQLite. Izmantojot citu sistēmu, piemēram, `pgsql:`, savienojums tiks atvērts, bet `Merql::init()` izraisīs `Unsupported database driver: pgsql`, līdz reģistrēsiet tai draiveri. Skatiet sadaļu [Datubāzu draiveri](https://merql.inline0.dev/lv/docs/apply/drivers).

## Momentuzņēmuma izveide

Momentuzņēmumā tiek saglabātas katras tabulas visas rindas (vai tabulu apakškopa) kā pirkstu nospiedumi un dati. Pirkstu nospiedumi ļauj ātri noteikt izmaiņas; pilni rindu dati ir pieejami, kad nepieciešams aprēķināt atšķirības.

```php
use Merql\Merql;

Merql::init($connection);

// Snapshot all tables.
$snapshot = Merql::snapshot('baseline');

// Snapshot specific tables.
$snapshot = Merql::snapshot('baseline', ['posts', 'comments', 'users']);
```

Momentuzņēmumi pēc noklusējuma tiek saglabāti kā JSON faili mapē `.merql/snapshots/`. Šo atrašanās vietu var mainīt:

```php
use Merql\Snapshot\SnapshotStore;

SnapshotStore::setDirectory('/var/data/merql-snapshots');
```

## Atšķirību aprēķināšana

Atšķirību aprēķins salīdzina divus momentuzņēmumus un atgriež izmaiņu kopu ar ievietošanas, atjaunināšanas un dzēšanas darbībām. Abiem momentuzņēmumiem jau jābūt izveidotiem; šeit `current` ir otrais momentuzņēmums, kas uzņemts pēc datubāzes izmaiņām.

```php
$changeset = Merql::diff('baseline', 'current');

echo "Inserts: " . count($changeset->inserts()) . "\n";
echo "Updates: " . count($changeset->updates()) . "\n";
echo "Deletes: " . count($changeset->deletes()) . "\n";

// Inspect changes for a single table.
$postChanges = $changeset->forTable('posts');

// Check per-column diffs on updates.
foreach ($changeset->updates() as $update) {
    echo "Row {$update->rowKey} in {$update->table}:\n";
    foreach ($update->columnDiffs as $diff) {
        echo "  {$diff->column}: {$diff->oldValue} -> {$diff->newValue}\n";
    }
}
```

## Trīspusēja apvienošana

Galvenā darbība. Ņem trīs nosauktus momentuzņēmumus (base, ours, theirs) un apvieno abas izmaiņu kopas.

Base ir stāvoklis, no kura sāka abas kopijas. Ours un theirs ir abas kopijas pēc neatkarīgām izmaiņām, tāpēc katra jāiegūst no datubāzes, kurā tā atrodas. Piemēram, ja smilškaste tika izveidota, kopējot reāllaika datubāzi:

```php
use Merql\Connection;
use Merql\Merql;

$live    = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');

Merql::init($live);
Merql::snapshot('base');      // before the sandbox is copied from live

// ... copy live.sqlite to sandbox.sqlite, then both databases change ...

Merql::init($sandbox);
Merql::snapshot('ours');      // the sandbox with our edits
Merql::init($live);
Merql::snapshot('theirs');    // live with its own changes

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

echo "Operations: {$result->operationCount()}\n";
echo "Conflicts:  {$result->conflictCount()}\n";
echo "Clean:      " . ($result->isClean() ? 'yes' : 'no') . "\n";
```

Neveidojiet visus trīs momentuzņēmumus secīgi no vienas datubāzes. Tādā gadījumā theirs jau ietvertu ours izmaiņas, un katra ours ievietotā rinda tiktu uzrādīta kā `insert_insert` konflikts.

`Merql::merge()` apvieno kolonnu līmenī. Lai apvienotu arī TEXT un JSON vērtību saturu, izmantojiet `ThreeWayMerge` kopā ar `CellMergeConfig`; skatiet sadaļu [Šūnu līmeņa apvienošana](https://merql.inline0.dev/lv/docs/merge/cell-level).

### Konfliktu risināšana

Ja abas puses maina vienu un to pašu kolonnu uz atšķirīgām vērtībām, merql ziņo par konfliktu. Konfliktus var atrisināt automātiski vai pārbaudīt manuāli.

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

if (!$result->isClean()) {
    // Auto-resolve: one side wins.
    $resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

    // Or inspect each conflict.
    foreach ($result->conflicts() as $conflict) {
        echo "Conflict on {$conflict->table()} row {$conflict->rowKey()}:\n";
        echo "  Type:   {$conflict->type()}\n";
        echo "  Column: {$conflict->column()}\n";
        echo "  Ours:   " . var_export($conflict->oursValue(), true) . "\n";
        echo "  Theirs: " . var_export($conflict->theirsValue(), true) . "\n";
    }
}
```

## Apvienošanas lietošana

Kad apvienošanas rezultātā vairs nav neatrisinātu konfliktu, lietojiet to datubāzē, kas nodota `Merql::init()`.

Apvienošanas rezultātā iekļautās darbības pārveido base par apvienoto stāvokli, ietverot abu pušu izmaiņas. `Merql::apply()` izpilda tās visas, tāpēc darbības var veiksmīgi lietot datubāzē, kurā joprojām ir base stāvoklis. Lietojot tās datubāzē, kurā jau ir vienas puses izmaiņas, šīs puses ievietošana mēģinās pievienot jau esošu rindu, neizdosies un atcels visu apvienošanu. Lai pārnestu vienas puses izmaiņas datubāzē, kurā jau ir otras puses izmaiņas (piemēram, smilškastes izmaiņas uz reāllaika datubāzi), lietojiet tikai trūkstošās darbības, izmantojot [Nosacītā lietošana](https://merql.inline0.dev/lv/docs/apply/guarded).

```php
$applied = Merql::apply($result);

echo "Rows affected: {$applied->rowsAffected()}\n";

if ($applied->hasErrors()) {
    foreach ($applied->errors() as $error) {
        echo "Error: {$error}\n";
    }
}
```

Lietotājs ietver visas darbības transakcijā. Ja kāds priekšraksts neizdodas, visa apvienošana tiek atcelta, un kļūda tiek atgriezta laukā `errors()`. Šādā gadījumā `rowsAffected()` joprojām uzskaita priekšrakstus, kas izpildīti pirms kļūmes, lai gan tie tika atcelti ([#4](https://github.com/inline0/merql/issues/4)); vispirms pārbaudiet `hasErrors()`.

## Izmēģinājuma izpilde

Priekšskatiet SQL, ko ģenerētu apvienošana, to neizpildot.

```php
use Merql\Apply\DryRun;

$statements = DryRun::generate($result);

foreach ($statements as $sql) {
    echo "{$sql};\n";
}
```

## Divpusējs ielāps

Ja pieejams tikai base un izmaiņu kopa (bez vienlaicīgām "ours" izmaiņām), izmantojiet `patch()`. Tas ir līdzvērtīgi apvienošanai, kurā ours nav mainījies salīdzinājumā ar base.

```php
$result = Merql::patch('base', 'changes');
// Always clean: no concurrent edits means no conflicts.
Merql::apply($result);
```

## CLI izmantošana

Visas šīs darbības pieejamas arī komandrindā. Pilnu informāciju skatiet [CLI atsaucē](https://merql.inline0.dev/lv/docs/cli).

```bash
export MERQL_DB_DSN="sqlite:mydb.sqlite"

vendor/bin/merql snapshot base
# ... change the database ...
vendor/bin/merql snapshot current
vendor/bin/merql diff base current
```

Trīspusējai apvienošanai iegūstiet katru momentuzņēmumu no datubāzes ar atbilstošo stāvokli un pēc tam veiciet apvienošanu:

```bash
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base
MERQL_DB_DSN="sqlite:/data/sandbox.sqlite" vendor/bin/merql snapshot ours
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot theirs
vendor/bin/merql merge base ours theirs --dry-run
```

## Tabulu un kolonnu filtrēšana

Momentuzņēmumos var izslēgt tabulas (piemēram, kešatmiņas vai sesiju tabulas) un ignorēt mainīgas kolonnas (piemēram, `updated_at` laika zīmogus).

```php
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

$snapshot = $snapshotter->capture('baseline', filters: [
    TableFilter::exclude(['sessions', 'cache_*']),
    ColumnFilter::ignore(['updated_at', 'modified_date']),
]);
```

## Nākamās darbības

- Pilnu komandrindas saskarnes atsauci skatiet [CLI atsaucē](https://merql.inline0.dev/lv/docs/cli).
- Detalizētu visu klašu dokumentāciju skatiet [PHP API atsaucē](https://merql.inline0.dev/lv/docs/api).
