
# Generování SQL

`SqlGenerator` převádí `MergeResult` na parametrizované příkazy SQL. Všechny hodnoty se předávají jako parametry; nikdy se nevkládají přímo do řetězce 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']
}
```

Použijte `generateGuarded()`, když se má zkontrolovaný plán sloučení použít pouze tehdy, pokud aktuální řádky stále odpovídají očekávanému snímku.

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

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

## Formát příkazů

Každý příkaz je asociativní pole se dvěma klíči:

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

Identifikátory (názvy tabulek a sloupců) se uzavírají podle stylu uvozování databázového ovladače. MySQL používá zpětné apostrofy, SQLite používá dvojité uvozovky. Pokud ovladač není zadán, standardně se použijí zpětné apostrofy.

## Pořadí operací

Operace se seskupují podle typu a provádějí v tomto pořadí:

1. Nejprve **INSERTs** — nové řádky musí existovat předtím, než se na ně mohou odkazovat aktualizace.
2. Poté **UPDATEs**.
3. Nakonec **DELETEs** — před odstraněním je třeba zrušit odkazy na řádky.

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

## Řazení podle cizích klíčů

Jsou-li zadány závislosti cizích klíčů, operace v každé skupině se seřadí topologicky. Při vkládání a aktualizaci se nejprve zpracují nadřazené tabulky, před podřízenými. Při odstranění se nejprve zpracují podřízené tabulky, před nadřazenými.

```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` provádí topologické řazení grafu závislostí. Kruhové závislosti detekuje a přeruší, aby se zabránilo nekonečným smyčkám.

### Automatická detekce FK

`Applier` automaticky načítá vztahy cizích klíčů z databáze:

```php
use Merql\Apply\Applier;

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

## Ukázky generovaného SQL

### INSERT

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

Zahrnou se všechny sloupce z hodnot operace.

### UPDATE

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

Sloupce identity (primární klíč) jsou vyloučeny z klauzule SET a použity v klauzuli WHERE. Hodnoty pro WHERE se získají dekódováním klíče řádku.

### DELETE

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

V klauzuli WHERE se objeví pouze sloupce identity.

### Chráněná UPDATE

Chráněné aktualizace obsahují klauzule identity i klauzule pro očekávaný aktuální řádek.

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

Pokud řádek již nemá očekávané `status`, příkaz ovlivní nula řádků a chráněné použití vrátí transakci zpět.

### Chráněná INSERT

Chráněné vložení používá `NOT EXISTS`, aby nevložilo řádek s identitou, která se objevila od vytvoření plánu.

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

## Zpracování NULL

Hodnoty NULL se předávají jako PHP `null` v poli parametrů. Adaptér připojení se postará o navázání parametrů:

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

Ve výstupu náhledu nanečisto se hodnoty NULL zobrazují jako řetězec `NULL` bez uvozovek.

## Detekce sloupců identity

`SqlGenerator` určuje sloupce tvořící identitu řádku podle `TableSnapshot::identityColumns` základního snímku. Tyto sloupce se používají v klauzulích WHERE příkazů UPDATE a DELETE a jsou vyloučeny z klauzulí SET příkazů UPDATE.

Pokud základní snímek není k dispozici, jako náhradní řešení se použije první sloupec v poli hodnot. Pokud je pole hodnot prázdné, předpokládá se `id`.

## Předání ovladače

Chcete-li řídit uvozování identifikátorů, předejte instanci `Driver`:

```php
use Merql\Driver\SqliteDriver;

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