
# Primeiros passos

Este guia apresenta a instalação, a conexão a um banco de dados, a captura de snapshots, o cálculo de diferenças e a realização de uma mesclagem de três vias.

## Instalação

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

merql requer PHP 8.2+ com `ext-mbstring` e `ext-zlib` (ambos exigidos pela dependência `pitmaster/pitmaster`). Você também precisa da extensão do adaptador utilizado: `ext-pdo` com `ext-pdo_sqlite` ou `ext-pdo_mysql` para o adaptador PDO, ou `ext-mysqli` para o adaptador mysqli. Composer apenas sugere essas extensões, então verifique se a necessária está instalada.

O pacote instala o CLI como `vendor/bin/merql`. Use merql 0.4.1 ou posterior
para o CLI; nas versões anteriores, o binário não conseguia encontrar o autoloader quando
o merql era instalado como dependência.

## Conexão a um banco de dados

merql funciona por meio de uma pequena interface de adaptador de conexão. Há adaptadores integrados
para PDO e mysqli, com drivers de dialeto integrados para MySQL e 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()` abre qualquer DSN compatível com PDO, mas o merql inclui drivers de dialeto
apenas para MySQL e SQLite. Com outro mecanismo, como `pgsql:`, a conexão
é aberta, mas `Merql::init()` lança `Unsupported database driver: pgsql` até que você
registre um driver para ele. Consulte [Drivers de banco de dados](https://merql.inline0.dev/pt/docs/apply/drivers).

## Captura de um snapshot

Um snapshot registra cada linha de cada tabela (ou de um subconjunto de tabelas) como impressões digitais e dados. As impressões digitais permitem detectar alterações rapidamente; os dados completos das linhas ficam disponíveis quando é preciso calcular um diff.

```php
use Merql\Merql;

Merql::init($connection);

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

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

Por padrão, os snapshots são salvos como arquivos JSON em `.merql/snapshots/`. Você pode alterar esse local:

```php
use Merql\Snapshot\SnapshotStore;

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

## Cálculo de um diff

Um diff compara dois snapshots e retorna um conjunto de alterações com inserções, atualizações e exclusões. Os dois snapshots já precisam existir; aqui, `current` é um segundo snapshot capturado depois que o banco de dados foi alterado.

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

## Mesclagem de três vias

A operação principal. Recebe três snapshots nomeados (base, nosso, deles) e mescla os dois conjuntos de alterações.

A base é o estado inicial comum às duas cópias. Nosso e deles são as duas cópias após terem sido alteradas de forma independente, então cada uma precisa ser capturada do banco de dados que a contém. Por exemplo, quando uma sandbox foi copiada de um banco de dados ativo:

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

Não capture os três snapshots do mesmo banco de dados, em sequência. Nesse caso, deles já conteria as alterações de nosso, e cada linha inserida por nosso seria identificada como um conflito `insert_insert`.

`Merql::merge()` faz a mesclagem no nível das colunas. Para também mesclar dentro dos valores de TEXT e JSON, use `ThreeWayMerge` com um `CellMergeConfig`; consulte [Mesclagem no nível da célula](https://merql.inline0.dev/pt/docs/merge/cell-level).

### Como lidar com conflitos

Quando os dois lados alteram a mesma coluna para valores diferentes, o merql gera um conflito. Você pode resolver conflitos automaticamente ou inspecioná-los manualmente.

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

## Aplicação da mesclagem

Depois de obter um resultado de mesclagem limpo (sem conflitos não resolvidos), aplique-o ao banco de dados passado para `Merql::init()`.

As operações do resultado da mesclagem transformam a base no estado mesclado e incluem as alterações dos dois lados. `Merql::apply()` executa todas elas; por isso, a aplicação funciona sem problemas em um banco de dados que ainda contém o estado base. Se aplicado a um banco de dados que já contém as alterações de um dos lados, qualquer inserção desse lado encontrará uma linha existente, falhará e reverterá toda a mesclagem. Para incorporar as alterações de um lado a um banco de dados que já contém as alterações do outro (por exemplo, alterações da sandbox para o ambiente ativo), aplique apenas as operações que faltam usando [Aplicação protegida](https://merql.inline0.dev/pt/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";
    }
}
```

O aplicador envolve todas as operações em uma transação. Se alguma instrução falhar, toda a mesclagem será revertida e o erro será retornado em `errors()`. Nesse caso, `rowsAffected()` ainda conta as instruções executadas antes da falha, embora tenham sido revertidas ([#4](https://github.com/inline0/merql/issues/4)); verifique primeiro `hasErrors()`.

## Simulação

Visualize o SQL que uma mesclagem geraria, sem executá-la.

```php
use Merql\Apply\DryRun;

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

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

## Patch de duas vias

Quando você só tem uma base e um conjunto de alterações (sem modificações simultâneas de “nosso”), use `patch()`. Isso equivale a uma mesclagem em que nosso permanece inalterado em relação à base.

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

## Usando o CLI

Todas essas operações também estão disponíveis na linha de comando. Consulte a [ReferênciaCLI](https://merql.inline0.dev/pt/docs/cli) para ver todos os detalhes.

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

Em uma mesclagem de três vias, capture cada snapshot do banco de dados que contém aquele estado e, em seguida, faça a mesclagem:

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

## Filtragem de tabelas e colunas

Os snapshots podem excluir tabelas (como tabelas de cache ou sessão) e ignorar colunas voláteis (como marcas de data e hora `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']),
]);
```

## Próximas etapas

- [ReferênciaCLI](https://merql.inline0.dev/pt/docs/cli) para a interface completa de linha de comando.
- [PHP APIReferência](https://merql.inline0.dev/pt/docs/api) para documentação detalhada de cada classe e método.
