
# Generazione di SQL

`SqlGenerator` converte un `MergeResult` in istruzioni SQL parametrizzate. Tutti i valori vengono passati come parametri, mai interpolati nella stringa SQL.

```php
use Merql\Apply\SqlGenerator;

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

foreach ($statements as $stmt) {
    echo $stmt['sql'] . "\n";
    // INSERT INTO `posts` (`id`, `title`, `status`) VALUES (?, ?, ?)
    print_r($stmt['params']);
    // [42, 'New Title', 'publish']
}
```

Usate `generateGuarded()` quando un piano di fusione revisionato deve essere applicato solo se le righe live corrispondono ancora a uno snapshot atteso.

```php
use Merql\Apply\SqlGenerator;
use Merql\Snapshot\SnapshotStore;

$statements = SqlGenerator::generateGuarded(
    $selectedResult,
    SnapshotStore::load('theirs'),
);
```

## Formato delle istruzioni

Ogni istruzione è un array associativo con due chiavi:

```php
[
    'sql'    => 'INSERT INTO `posts` (`id`, `title`) VALUES (?, ?)',
    'params' => [42, 'New Title'],
]
```

Gli identificatori (nomi di tabelle e colonne) vengono quotati secondo lo stile del driver del database. MySQL usa i backtick, SQLite usa le virgolette doppie. Se non viene specificato un driver, per impostazione predefinita si usano i backtick.

## Ordine delle operazioni

Le operazioni sono raggruppate per tipo ed eseguite in questo ordine:

1. Prima gli **INSERT**: le nuove righe devono esistere prima che gli aggiornamenti possano farvi riferimento.
2. Poi gli **UPDATE**.
3. Infine i **DELETE**: prima di eliminare le righe è necessario rimuovere i riferimenti a esse.

```php
$statements = SqlGenerator::generate($result);
// All INSERTs, then all UPDATEs, then all DELETEs.
```

## Ordinamento delle chiavi esterne

Quando vengono fornite le dipendenze delle chiavi esterne, le operazioni all'interno di ogni gruppo vengono ordinate topologicamente. Per gli inserimenti e gli aggiornamenti, le tabelle padre vengono elaborate prima di quelle figlie. Per le eliminazioni, le tabelle figlie vengono elaborate prima di quelle padre.

```php
$fkDependencies = [
    'comments' => ['posts'],        // comments references posts
    'post_meta' => ['posts'],       // post_meta references posts
];

$statements = SqlGenerator::generate($result, fkDependencies: $fkDependencies);
// INSERTs: posts first, then comments and post_meta
// DELETEs: comments and post_meta first, then posts
```

`ForeignKeyResolver` esegue un ordinamento topologico sul grafo delle dipendenze. Le dipendenze circolari vengono rilevate e interrotte per evitare cicli infiniti.

### Rilevamento automatico delle chiavi esterne

`Applier` legge automaticamente dal database le relazioni tra chiavi esterne:

```php
use Merql\Apply\Applier;

$applier = new Applier($connection);
$applied = $applier->apply($result);
// FK dependencies are read from the database via the driver.
```

## Esempi di SQL generati

### INSERT

```sql
INSERT INTO `posts` (`id`, `title`, `content`, `status`) VALUES (?, ?, ?, ?)
-- params: [42, 'New Post', 'Body text', 'draft']
```

Vengono incluse tutte le colonne dei valori dell'operazione.

### UPDATE

```sql
UPDATE `posts` SET `title` = ?, `status` = ? WHERE `id` = ?
-- params: ['New Title', 'publish', 42]
```

Le colonne di identità (chiave primaria) sono escluse dalla clausola SET e usate nella clausola WHERE. I valori della clausola WHERE provengono dalla decodifica della chiave della riga.

### DELETE

```sql
DELETE FROM `posts` WHERE `id` = ?
-- params: [42]
```

Nella clausola WHERE compaiono solo le colonne di identità.

### UPDATE protetto

Gli aggiornamenti protetti includono sia le clausole di identità sia quelle per le righe correnti attese.

```sql
UPDATE `posts` SET `title` = ? WHERE `id` = ? AND `status` = ?
-- params: ['New Title', 42, 'draft']
```

Se la riga non ha più `status` previsto, l'istruzione interessa zero righe e l'applicazione protetta annulla la transazione.

### INSERT protetto

Gli inserimenti protetti usano `NOT EXISTS` per evitare di inserire una riga con un'identità già comparsa dopo la creazione del piano.

```sql
INSERT INTO `posts` (`id`, `title`) SELECT ?, ? WHERE NOT EXISTS (
  SELECT 1 FROM `posts` WHERE `id` = ?
)
-- params: [42, 'New Post', 42]
```

## Gestione di NULL

I valori NULL vengono passati come PHP `null` nell'array dei parametri. L'adapter della connessione gestisce il binding:

```php
$statements = SqlGenerator::generate($result);
// params might contain: [42, null, 'draft']
// The adapter binds null as SQL NULL
```

Nell'output dell'esecuzione di prova, i valori NULL vengono visualizzati come stringa letterale `NULL` senza virgolette.

## Rilevamento delle colonne di identità

`SqlGenerator` determina quali colonne costituiscono l'identità della riga esaminando `TableSnapshot::identityColumns` dello snapshot di base. Queste colonne vengono usate nelle clausole WHERE delle istruzioni UPDATE e DELETE ed escluse dalle clausole SET degli UPDATE.

Se non è disponibile uno snapshot di base, viene usata come fallback la prima colonna dell'array dei valori. Se l'array dei valori è vuoto, si presume `id`.

## Passare un driver

Per controllare la quotatura degli identificatori, passate un'istanza di `Driver`:

```php
use Merql\Driver\SqliteDriver;

$statements = SqlGenerator::generate(
    $result,
    driver: new SqliteDriver(),
);
// Uses double-quote quoting: "posts", "title"
```
