
# Generowanie SQL

`SqlGenerator` przekształca `MergeResult` w parametryzowane instrukcje SQL. Wszystkie wartości są przekazywane jako parametry, nigdy nie są wstawiane bezpośrednio do ciągu 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']
}
```

Użyj `generateGuarded()`, gdy sprawdzony plan scalania powinien zostać zastosowany tylko wtedy, gdy bieżące wiersze nadal odpowiadają oczekiwanemu zrzutowi.

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

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

## Format instrukcji

Każda instrukcja to tablica asocjacyjna z dwoma kluczami:

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

Identyfikatory (nazwy tabel i kolumn) są cytowane zgodnie ze stylem sterownika bazy danych. MySQL używa znaków odwrotnego apostrofu, a SQLite używa podwójnych cudzysłowów. Jeśli nie podano sterownika, domyślnie używane są znaki odwrotnego apostrofu.

## Kolejność operacji

Operacje są grupowane według typu i wykonywane w następującej kolejności:

1. Najpierw **INSERTs** — nowe wiersze muszą istnieć, zanim odwołają się do nich aktualizacje.
2. Następnie **UPDATEs**.
3. Na końcu **DELETEs** — przed usunięciem trzeba usunąć odwołania do wierszy.

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

## Kolejność kluczy obcych

Jeśli podano zależności kluczy obcych, operacje w każdej grupie są sortowane topologicznie. Tabele nadrzędne są przetwarzane przed podrzędnymi przy wstawianiu i aktualizowaniu. Przy usuwaniu tabele podrzędne są przetwarzane przed nadrzędnymi.

```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` wykonuje sortowanie topologiczne grafu zależności. Wykrywa i przerywa zależności cykliczne, aby zapobiec nieskończonym pętlom.

### Automatyczne wykrywanie kluczy obcych

`Applier` automatycznie odczytuje z bazy danych relacje kluczy obcych:

```php
use Merql\Apply\Applier;

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

## Przykłady wygenerowanych SQL

### INSERT

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

Uwzględniane są wszystkie kolumny z wartości przekazanych w operacji.

### UPDATE

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

Kolumny identyfikacyjne (klucz główny) są pomijane w klauzuli SET i używane w klauzuli WHERE. Wartości klauzuli WHERE pochodzą z dekodowania klucza wiersza.

### DELETE

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

W klauzuli WHERE występują wyłącznie kolumny identyfikacyjne.

### UPDATE z kontrolą

Aktualizacje z kontrolą zawierają zarówno klauzule dotyczące tożsamości, jak i klauzule dotyczące oczekiwanego bieżącego wiersza.

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

Jeśli wiersz nie ma już oczekiwanej `status`, instrukcja nie zmienia żadnych wierszy, a zabezpieczone zastosowanie wycofuje transakcję.

### Zabezpieczony INSERT

Zabezpieczone wstawienia używają `NOT EXISTS`, aby uniknąć wstawienia do tożsamości, która pojawiła się po utworzeniu planu.

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

## Obsługa NULL

Wartości NULL są przekazywane jako PHP `null` w tablicy params. Adapter połączenia obsługuje wiązanie parametrów:

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

W wynikach próbnego uruchomienia wartości NULL są wyświetlane jako literał `NULL` bez cudzysłowów.

## Wykrywanie kolumny tożsamości

`SqlGenerator` ustala, które kolumny tworzą tożsamość wiersza, na podstawie `TableSnapshot::identityColumns` migawki bazowej. Kolumny te są używane w klauzulach WHERE instrukcji UPDATE i DELETE oraz pomijane w klauzulach SET instrukcji UPDATE.

Jeśli migawka bazowa jest niedostępna, jako rozwiązanie awaryjne używana jest pierwsza kolumna w tablicy values. Jeśli tablica values jest pusta, zakłada się `id`.

## Przekazywanie sterownika

Aby kontrolować sposób cytowania identyfikatorów, przekaż instancję `Driver`:

```php
use Merql\Driver\SqliteDriver;

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