
# Първи стъпки

Това ръководство описва инсталирането, свързването с база данни, създаването на моментни снимки, изчисляването на разлики и извършването на тристранно сливане.

## Инсталиране

```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 поддържана DSN връзка, но merql включва драйвери за диалекти само за MySQL и SQLite. При друга машина за бази данни, например `pgsql:`, връзката се отваря, но `Merql::init()` хвърля `Unsupported database driver: pgsql`, докато не регистрирате драйвер за нея. Вижте [Драйвери за бази данни](https://merql.inline0.dev/bg/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";
    }
}
```

## Тристранно сливане

Основната операция. Взема три наименувани моментни снимки (базова, наша, тяхна) и слива двата набора от промени.

Базовата моментна снимка е състоянието, от което са започнали и двете копия. Нашето и тяхното копие са двете версии след независими промени, затова всяко трябва да бъде заснето от базата данни, която го съдържа. Например, когато тестова среда е създадена като копие на активна база данни:

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

Не създавайте и трите моментни снимки последователно от една база данни. Тогава тяхната версия вече ще съдържа промените на нашата и всеки ред, вмъкнат от нашата страна, ще се появи като конфликт `insert_insert`.

`Merql::merge()` извършва сливане на ниво колона. За сливане и вътре в стойности TEXT и JSON използвайте `ThreeWayMerge` с `CellMergeConfig`; вижте [Сливане на ниво клетка](https://merql.inline0.dev/bg/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()` изпълнява всяка от тях, затова се прилага без проблем към база данни, която все още е в базовото състояние. Ако се приложи към база данни, в която вече има промените от едната страна, всяко добавяне от тази страна среща вече съществуващ ред, се проваля и връща обратно цялото сливане. За да прехвърлите промените от едната страна в база данни, в която вече са налични промените от другата (например редакции в тестова среда към работната среда), приложете само операциите, които липсват в нея, с [Guarded Apply](https://merql.inline0.dev/bg/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";
}
```

## Двустранна корекция

Когато разполагате само с база и набор от промени (без едновременни промени от „нашата“ страна), използвайте `patch()`. Това е равносилно на сливане, при което нашето състояние не се е променило спрямо базовото.

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

## Използване на CLI

Всички тези операции са достъпни и от командния ред. За пълни подробности вижте [CLI справка](https://merql.inline0.dev/bg/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/bg/docs/cli) за пълния интерфейс на командния ред.
- [PHP API справка](https://merql.inline0.dev/bg/docs/api) за подробна документация на всеки клас и метод.
