
# Початок роботи

Цей посібник описує встановлення, підключення до бази даних, створення знімків, обчислення різниць і виконання тристороннього злиття.

## Встановлення

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

Для merql потрібен PHP 8.2+ з `ext-mbstring` і `ext-zlib` (обидва потрібні залежності `pitmaster/pitmaster`). Також потрібне розширення для використовуваного адаптера: `ext-pdo` з `ext-pdo_sqlite` або `ext-pdo_mysql` для адаптера PDO чи `ext-mysqli` для адаптера mysqli. Composer лише рекомендує їх, тому перевірте, чи встановлено потрібне.

Пакет установлює CLI як `vendor/bin/merql`. Для CLI використовуйте merql 0.4.1 або новішу версію; у попередніх випусках виконуваний файл не міг знайти автозавантажувач, якщо merql було встановлено як залежність.

## Підключення до бази даних

merql працює через невеликий інтерфейс адаптера підключення. Вбудовані адаптери доступні для PDO та mysqli, а вбудовані драйвери діалектів — для MySQL і 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()` відкриває будь-який DSN, який підтримує PDO, але merql містить драйвери діалектів лише для MySQL і SQLite. З іншим рушієм, наприклад `pgsql:`, підключення відкриється, але `Merql::init()` видасть `Unsupported database driver: pgsql`, доки ви не зареєструєте для нього драйвер. Див. [Драйвери баз даних](https://merql.inline0.dev/uk/docs/apply/drivers).

## Створення знімка

Знімок записує кожен рядок кожної таблиці (або вибраної підмножини таблиць) як відбитки та дані. Відбитки дають змогу швидко виявляти зміни; повні дані рядків доступні, коли потрібна різниця.

```php
use Merql\Merql;

Merql::init($connection);

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

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

За замовчуванням знімки зберігаються як файли JSON у `.merql/snapshots/`. Це розташування можна змінити:

```php
use Merql\Snapshot\SnapshotStore;

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

## Обчислення різниці

Різниця порівнює два знімки й повертає набір змін: вставлення, оновлення та видалення. Обидва знімки мають уже існувати; тут `current` — це другий знімок, зроблений після зміни бази даних.

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

## Тристороннє злиття

Основна операція. Вона бере три іменовані знімки (base, ours, theirs) і об’єднує два набори змін.

Base — це стан, з якого почали обидві копії. Ours і theirs — дві копії після незалежних змін, тому кожну потрібно знімати з бази даних, у якій вона зберігається. Наприклад, коли ізольоване середовище скопіювали з робочої бази даних:

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

Не створюйте всі три знімки послідовно з однієї бази даних. Тоді theirs уже міститиме зміни ours, а кожен вставлений ours рядок буде визначено як конфлікт `insert_insert`.

`Merql::merge()` виконує злиття на рівні стовпців. Щоб також об’єднувати вміст значень TEXT і JSON, використовуйте `ThreeWayMerge` з `CellMergeConfig`; див. [Злиття на рівні комірок](https://merql.inline0.dev/uk/docs/merge/cell-level).

### Опрацювання конфліктів

Якщо обидві сторони змінюють той самий стовпець на різні значення, merql повідомляє про конфлікт. Ви можете розв’язати конфлікти автоматично або перевірити їх вручну.

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

## Застосування злиття

Коли результат злиття готовий (невирішених конфліктів немає), застосуйте його до бази даних, переданої в `Merql::init()`.

Операції в результаті злиття перетворюють base на об’єднаний стан і охоплюють зміни обох сторін. `Merql::apply()` виконує кожну з них, тож результат можна застосувати без помилок до бази даних, у якій досі зберігається стан base. Якщо застосувати його до бази даних, яка вже містить зміни однієї зі сторін, будь-яке вставлення з цієї сторони зіткнеться з наявним рядком, завершиться помилкою і скасує все злиття. Щоб перенести зміни однієї сторони в базу даних, яка вже містить зміни іншої (наприклад, зміни в ізольованому середовищі — у робочу базу), застосуйте лише відсутні в ній операції за допомогою [Захищене застосування](https://merql.inline0.dev/uk/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";
    }
}
```

Засіб застосування загортає всі операції в транзакцію. Якщо будь-який оператор завершується помилкою, усе злиття відкочується, а помилка повертається в `errors()`. У такому разі `rowsAffected()` усе одно враховує оператори, виконані до помилки, хоча їх було відкочено ([#4](https://github.com/inline0/merql/issues/4)); спершу перевірте `hasErrors()`.

## Тестовий запуск

Перегляньте SQL, які згенерувало б злиття, не виконуючи його.

```php
use Merql\Apply\DryRun;

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

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

## Двосторонній патч

Якщо у вас є лише base і набір змін (без паралельних змін «ours»), використовуйте `patch()`. Це рівнозначно злиттю, у якому ours не відрізняється від base.

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

## Використання CLI

Усі ці операції також доступні в командному рядку. Повний опис див. у [CLI Довіднику](https://merql.inline0.dev/uk/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
```

Для тристороннього злиття створіть кожен знімок із бази даних, у якій зберігається відповідний стан, а потім виконайте злиття:

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

## Фільтрування таблиць і стовпців

Зі знімків можна виключити таблиці (наприклад, таблиці кешу або сеансів) і ігнорувати мінливі стовпці (наприклад, позначки часу `updated_at`).

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

## Наступні кроки

- Повний опис інтерфейсу командного рядка — у [CLI Довіднику](https://merql.inline0.dev/uk/docs/cli).
- Детальна документація кожного класу й методу — у [PHP API Довіднику](https://merql.inline0.dev/uk/docs/api).
