
# Начало работы

В этом руководстве описаны установка, подключение к базе данных, создание снимков, вычисление различий и выполнение слияния трёх ветвей.

## Установка

```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`. Используйте merql 0.4.1 или более поздней версии для CLI; в предыдущих выпусках двоичный файл не мог найти автозагрузчик, если 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()` открывает любое подключение PDO, но в merql есть драйверы диалектов только для MySQL и SQLite. Для другого движка, например `pgsql:`, подключение установится, но `Merql::init()` будет выдавать `Unsupported database driver: pgsql`, пока вы не зарегистрируете для него драйвер. См. раздел [Драйверы баз данных](https://merql.inline0.dev/ru/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/ru/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()`.

Операции в результате слияния преобразуют базовое состояние в итоговое и включают изменения обеих сторон. `Merql::apply()` выполняет их все, поэтому результат можно применить без ошибок к базе данных, в которой ещё хранится базовое состояние. При применении к базе данных, уже содержащей изменения одной стороны, любая вставка с этой стороны встретит уже существующую строку, завершится ошибкой и отменит всё слияние. Чтобы перенести изменения одной стороны в базу данных, уже содержащую изменения другой стороны (например, изменения в песочнице в рабочую базу), примените только недостающие операции с помощью [Применение с проверкой](https://merql.inline0.dev/ru/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";
}
```

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

Если у вас есть только база и набор изменений (без параллельных изменений 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/ru/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/ru/docs/cli) по полному интерфейсу командной строки.
- [PHP APIСправочник](https://merql.inline0.dev/ru/docs/api) с подробным описанием каждого класса и метода.
