
# Pierwsze kroki

Ten przewodnik opisuje instalację, łączenie się z bazą danych, tworzenie migawek, obliczanie różnic i przeprowadzanie scalenia trójstronnego.

## Instalacja

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

merql wymaga PHP 8.2+ z `ext-mbstring` i `ext-zlib` (oba są wymagane przez zależność `pitmaster/pitmaster`). Potrzebujesz też rozszerzenia dla używanego adaptera: `ext-pdo` z `ext-pdo_sqlite` lub `ext-pdo_mysql` dla adaptera PDO albo `ext-mysqli` dla adaptera mysqli. Composer jedynie sugeruje te rozszerzenia, sprawdź więc, czy wymagane jest zainstalowane.

Pakiet instaluje CLI jako `vendor/bin/merql`. Użyj merql 0.4.1 lub nowszego dla CLI; wcześniejsze wydania nie mogły znaleźć autoloadera, gdy merql było zainstalowane jako zależność.

## Łączenie z bazą danych

merql korzysta z niewielkiego interfejsu adaptera połączenia. Wbudowane adaptery obsługują PDO i mysqli, a wbudowane sterowniki dialektów obsługują MySQL i 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()` otwiera każdą bazę danych obsługiwaną przez PDO za pomocą DSN, ale merql zawiera sterowniki dialektów tylko dla MySQL i SQLite. W przypadku innego silnika, takiego jak `pgsql:`, połączenie zostanie otwarte, ale `Merql::init()` zgłosi `Unsupported database driver: pgsql`, dopóki nie zarejestrujesz dla niego sterownika. Zobacz [Sterowniki baz danych](https://merql.inline0.dev/pl/docs/apply/drivers).

## Tworzenie migawki

Migawka zapisuje odciski palców i dane każdego wiersza z każdej tabeli (lub podzbioru tabel). Odciski umożliwiają szybkie wykrywanie zmian, a pełne dane wierszy są dostępne, gdy potrzebne jest obliczenie różnicy.

```php
use Merql\Merql;

Merql::init($connection);

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

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

Domyślnie migawki są zapisywane jako pliki JSON w katalogu `.merql/snapshots/`. Możesz zmienić tę lokalizację:

```php
use Merql\Snapshot\SnapshotStore;

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

## Obliczanie różnicy

Różnica porównuje dwie migawki i zwraca zestaw zmian obejmujący wstawienia, aktualizacje i usunięcia. Obie migawki muszą już istnieć; tutaj `current` to druga migawka utworzona po zmianie bazy danych.

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

## Scalenie trójstronne

Główna operacja. Pobiera trzy migawki o podanych nazwach (base, ours, theirs) i scala dwa zestawy zmian.

Base to stan, od którego rozpoczęły pracę obie kopie. Ours i theirs to dwie kopie po niezależnym wprowadzeniu zmian, dlatego każda musi być utworzona na podstawie bazy danych, w której się znajduje. Na przykład, gdy piaskownica została skopiowana z działającej bazy danych:

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

Nie twórz kolejno wszystkich trzech migawek z jednej bazy danych. Theirs zawierałaby już zmiany ours, więc każdy wiersz wstawiony przez ours zostałby zgłoszony jako konflikt `insert_insert`.

`Merql::merge()` scala na poziomie kolumn. Aby scalać także wewnątrz wartości TEXT i JSON, użyj `ThreeWayMerge` z `CellMergeConfig`; zobacz [Scalanie na poziomie komórek](https://merql.inline0.dev/pl/docs/merge/cell-level).

### Obsługa konfliktów

Gdy obie strony zmienią tę samą kolumnę na różne wartości, merql zgłasza konflikt. Możesz rozwiązać konflikty automatycznie lub przeanalizować je ręcznie.

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

## Zastosowanie scalenia

Gdy wynik scalenia jest czysty (nie ma nierozwiązanych konfliktów), zastosuj go do bazy danych przekazanej do `Merql::init()`.

Operacje w wyniku scalenia przekształcają stan base w stan scalony i obejmują zmiany z obu stron. `Merql::apply()` wykonuje każdą z nich, dlatego można je poprawnie zastosować do bazy danych, która wciąż zawiera stan base. Jeśli zastosujesz je do bazy zawierającej już zmiany jednej ze stron, wstawienie dotyczące tej strony trafi na istniejący wiersz, zakończy się błędem i wycofa całe scalenie. Aby przenieść zmiany jednej strony do bazy zawierającej już zmiany drugiej strony (na przykład zmiany z piaskownicy do działającej bazy), zastosuj tylko brakujące w niej operacje za pomocą [Zabezpieczone zastosowanie](https://merql.inline0.dev/pl/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";
    }
}
```

Program stosujący scalenie obejmuje wszystkie operacje transakcją. Jeśli dowolna instrukcja zakończy się błędem, całe scalenie zostanie wycofane, a błąd zwrócony w `errors()`. W takim przypadku `rowsAffected()` nadal uwzględnia instrukcje wykonane przed błędem, mimo że zostały wycofane ([#4](https://github.com/inline0/merql/issues/4)); najpierw sprawdź `hasErrors()`.

## Próbne uruchomienie

Wyświetl podgląd SQL, które wygenerowałoby scalenie, bez ich wykonywania.

```php
use Merql\Apply\DryRun;

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

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

## Łatka dwukierunkowa

Gdy masz tylko stan base i zestaw zmian (bez równoległych modyfikacji ours), użyj `patch()`. Odpowiada to scaleniu, w którym ours pozostało bez zmian względem base.

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

## Korzystanie z CLI

Wszystkie te operacje są również dostępne z wiersza poleceń. Szczegółowe informacje znajdziesz w [CLI dokumentacji](https://merql.inline0.dev/pl/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
```

W przypadku scalenia trójstronnego utwórz każdą migawkę na podstawie bazy danych zawierającej dany stan, a następnie przeprowadź scalanie:

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

## Filtrowanie tabel i kolumn

Podczas tworzenia migawek można wykluczyć tabele (np. tabele pamięci podręcznej lub sesji) i pominąć zmienne kolumny (np. znaczniki czasu `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']),
]);
```

## Następne kroki

- [CLI Dokumentacja](https://merql.inline0.dev/pl/docs/cli) pełnego interfejsu wiersza poleceń.
- [PHP API Dokumentacja](https://merql.inline0.dev/pl/docs/api) ze szczegółowym opisem każdej klasy i metody.
