
# Guida introduttiva

Questa guida illustra l'installazione, la connessione a un database, l'acquisizione di snapshot, il calcolo dei diff e l'esecuzione di un merge a tre vie.

## Installazione

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

merql richiede PHP 8.2+ con `ext-mbstring` e `ext-zlib` (entrambi richiesti dalla dipendenza `pitmaster/pitmaster`). Serve anche l'estensione per l'adapter in uso: `ext-pdo` con `ext-pdo_sqlite` o `ext-pdo_mysql` per l'adapter PDO, oppure `ext-mysqli` per l'adapter mysqli. Composer si limita a suggerirli, quindi verificate che sia installata l'estensione necessaria.

Il pacchetto installa CLI come `vendor/bin/merql`. Usate merql 0.4.1 o versioni successive per CLI; nelle versioni precedenti il binario non riusciva a trovare l'autoloader quando merql era installato come dipendenza.

## Connessione a un database

merql funziona tramite una piccola interfaccia adapter per le connessioni. Sono inclusi adapter per PDO e mysqli e driver di dialetto per 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()` apre qualsiasi DSN supportato da PDO, ma merql include driver di dialetto solo per MySQL e SQLite. Con un altro motore, come `pgsql:`, la connessione si apre, ma `Merql::init()` genera `Unsupported database driver: pgsql` finché non registrate un driver per quel motore. Consultate [Driver per database](https://merql.inline0.dev/it/docs/apply/drivers).

## Acquisizione di uno snapshot

Uno snapshot registra ogni riga di ogni tabella, o di un sottoinsieme di tabelle, sotto forma di impronte digitali e dati. Le impronte consentono di rilevare rapidamente le modifiche; i dati completi delle righe sono disponibili quando serve un 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']);
```

Gli snapshot vengono salvati per impostazione predefinita come file JSON in `.merql/snapshots/`. Potete cambiare questa posizione:

```php
use Merql\Snapshot\SnapshotStore;

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

## Calcolo di un diff

Un diff confronta due snapshot e restituisce un insieme di modifiche composto da inserimenti, aggiornamenti ed eliminazioni. Entrambi gli snapshot devono già esistere; qui `current` è un secondo snapshot acquisito dopo le modifiche al database.

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

## Merge a tre vie

È l'operazione principale. Prende tre snapshot con nome (base, ours, theirs) e unisce i due insiemi di modifiche.

Base è lo stato iniziale comune delle due copie. Ours e theirs sono le due copie dopo le rispettive modifiche indipendenti; ciascuno snapshot deve quindi essere acquisito dal database che contiene la copia corrispondente. Per esempio, se un ambiente sandbox è stato copiato da un database live:

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

Non acquisite tutti e tre gli snapshot dallo stesso database, uno dopo l'altro. Theirs conterrebbe già le modifiche di ours e ogni riga inserita da ours risulterebbe un conflitto `insert_insert`.

`Merql::merge()` esegue il merge a livello di colonna. Per unire anche i valori TEXT e JSON al loro interno, usate `ThreeWayMerge` con un `CellMergeConfig`; consultate [Merge a livello di cella](https://merql.inline0.dev/it/docs/merge/cell-level).

### Gestione dei conflitti

Se entrambi i lati modificano la stessa colonna assegnandole valori diversi, merql genera un conflitto. Potete risolvere i conflitti automaticamente oppure esaminarli 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";
    }
}
```

## Applicazione del merge

Una volta ottenuto un risultato di merge pulito, senza conflitti irrisolti, applicatelo al database passato a `Merql::init()`.

Le operazioni del risultato del merge trasformano lo stato base nello stato unito e includono le modifiche di entrambi i lati. `Merql::apply()` le esegue tutte, quindi si applicano correttamente a un database che contiene ancora lo stato base. Se applicate a un database che contiene già le modifiche di uno dei due lati, un inserimento di quel lato trova una riga già esistente, fallisce e annulla l'intero merge. Per trasferire le modifiche di un lato in un database che contiene già quelle dell'altro, ad esempio le modifiche della sandbox nel database live, applicate solo le operazioni mancanti con [Applicazione condizionale](https://merql.inline0.dev/it/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";
    }
}
```

L'applicatore racchiude tutte le operazioni in una transazione. Se un'istruzione non riesce, l'intero merge viene annullato e l'errore viene restituito in `errors()`. In tal caso, `rowsAffected()` conta comunque le istruzioni eseguite prima dell'errore, anche se sono state annullate ([#4](https://github.com/inline0/merql/issues/4)); controllate prima `hasErrors()`.

## Esecuzione di prova

Visualizzate in anteprima le SQL che un merge genererebbe, senza eseguirlo.

```php
use Merql\Apply\DryRun;

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

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

## Patch a due vie

Se disponete solo di una base e di un insieme di modifiche, senza modifiche concorrenti di «ours», usate `patch()`. Equivale a un merge in cui ours è invariato rispetto alla base.

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

## Uso di CLI

Tutte queste operazioni sono disponibili anche dalla riga di comando. Consultate il [CLI di riferimento](https://merql.inline0.dev/it/docs/cli) per tutti i dettagli.

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

Per un merge a tre vie, acquisite ogni snapshot dal database che contiene lo stato corrispondente, poi eseguite il merge:

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

## Filtri per tabelle e colonne

Gli snapshot possono escludere tabelle, come quelle di cache o sessione, e ignorare colonne volatili, come i timestamp `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']),
]);
```

## Passaggi successivi

- Consultate il [CLI di riferimento](https://merql.inline0.dev/it/docs/cli) per l'intera interfaccia a riga di comando.
- Consultate il [PHP API di riferimento](https://merql.inline0.dev/it/docs/api) per la documentazione dettagliata di ogni classe e metodo.
