
# Genereren van SQL

`SqlGenerator` zet een `MergeResult` om in geparametriseerde SQL-instructies. Alle waarden worden als parameters gebonden en nooit in de SQL-string ingevuld.

```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']
}
```

Gebruik `generateGuarded()` wanneer een beoordeeld samenvoegingsplan alleen mag worden toegepast als live rijen nog overeenkomen met een verwachte snapshot.

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

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

## Instructieformaat

Elke instructie is een associatieve array met twee sleutels:

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

Identifiers (tabel- en kolomnamen) worden geciteerd volgens de stijl van de databasedriver. MySQL gebruikt backticks en SQLite gebruikt dubbele aanhalingstekens. Als er geen driver is opgegeven, worden standaard backticks gebruikt.

## Volgorde van bewerkingen

Bewerkingen worden gegroepeerd op type en in deze volgorde uitgevoerd:

1. Eerst **INSERTs** -- nieuwe rijen moeten bestaan voordat ernaar kan worden verwezen in updates.
2. Daarna **UPDATEs**.
3. Tot slot **DELETEs** -- verwijzingen naar rijen moeten worden verwijderd voordat die rijen worden verwijderd.

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

## Volgorde op basis van foreign keys

Wanneer afhankelijkheden van foreign keys zijn opgegeven, worden bewerkingen binnen elke groep topologisch gesorteerd. Bovenliggende tabellen worden vóór onderliggende tabellen verwerkt bij inserts en updates. Bij deletes worden onderliggende tabellen vóór bovenliggende tabellen verwerkt.

```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` voert een topologische sortering uit op de afhankelijkheidsgraaf. Circulaire afhankelijkheden worden gedetecteerd en doorbroken om oneindige lussen te voorkomen.

### FK's automatisch detecteren

`Applier` leest automatisch relaties tussen foreign keys uit de database:

```php
use Merql\Apply\Applier;

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

## Voorbeelden van gegenereerde SQL

### INSERT

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

Alle kolommen uit de waarden van de bewerking worden opgenomen.

### UPDATE

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

Identiteitskolommen (primary key) worden uitgesloten van de SET-clausule en gebruikt in de WHERE-clausule. De waarden voor de WHERE-clausule worden verkregen door de rijsleutel te decoderen.

### DELETE

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

Alleen identiteitskolommen staan in de WHERE-clausule.

### Beveiligde UPDATE

Beveiligde updates bevatten zowel identiteitsclausules als clausules voor verwachte live rijen.

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

Als de rij niet langer de verwachte `status` heeft, beïnvloedt de instructie nul rijen en draait beveiligd toepassen de transactie terug.

### Beveiligde INSERT

Beveiligde inserts gebruiken `NOT EXISTS` om te voorkomen dat er wordt ingevoegd voor een identiteit die is verschenen nadat het plan is gemaakt.

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

## NULL verwerken

NULL-waarden worden als PHP `null` doorgegeven in de params-array. De verbindingsadapter verzorgt de binding:

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

In de uitvoer van een dry run worden NULL-waarden weergegeven als de letterlijke string `NULL`, zonder aanhalingstekens.

## Identiteitskolommen detecteren

`SqlGenerator` bepaalt welke kolommen de rij-identiteit vormen door naar `TableSnapshot::identityColumns` van de basissnapshot te kijken. Deze kolommen worden gebruikt voor WHERE-clausules bij UPDATE- en DELETE-instructies en uitgesloten van SET-clausules bij UPDATE's.

Als er geen basissnapshot beschikbaar is, wordt de eerste kolom in de values-array als terugvaloptie gebruikt. Als de values-array leeg is, wordt `id` aangenomen.

## Een driver doorgeven

Geef een instantie van `Driver` door om te bepalen hoe identifiers worden geciteerd:

```php
use Merql\Driver\SqliteDriver;

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