
# Генерування SQL

`SqlGenerator` перетворює `MergeResult` на параметризовані оператори SQL. Усі значення передаються як параметри, їх ніколи не вставляють безпосередньо в рядок 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']
}
```

Використовуйте `generateGuarded()`, якщо перевірений план злиття слід застосовувати лише за умови, що поточні рядки досі відповідають очікуваному знімку.

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

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

## Формат оператора

Кожен оператор — це асоціативний масив із двома ключами:

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

Ідентифікатори (назви таблиць і стовпців) беруться в лапки у стилі, який використовує драйвер бази даних. MySQL використовує зворотні лапки, а SQLite — подвійні. Якщо драйвер не вказано, за замовчуванням використовуються зворотні лапки.

## Порядок операцій

Операції групуються за типом і виконуються в такому порядку:

1. Спочатку **INSERTs** — нові рядки мають існувати, перш ніж на них зможуть посилатися оновлення.
2. Потім **UPDATEs**.
3. Наприкінці **DELETEs** — перед видаленням потрібно прибрати посилання на рядки.

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

## Порядок зовнішніх ключів

Якщо передано залежності зовнішніх ключів, операції в кожній групі впорядковуються топологічно. Під час вставлення й оновлення спочатку обробляються батьківські таблиці, а потім дочірні. Під час видалення спочатку обробляються дочірні таблиці, а потім батьківські.

```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` виконує топологічне сортування графа залежностей. Циклічні залежності виявляються й усуваються, щоб запобігти нескінченним циклам.

### Автоматичне виявлення FK

`Applier` автоматично зчитує зв’язки зовнішніх ключів із бази даних:

```php
use Merql\Apply\Applier;

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

## Приклади згенерованих SQL

### INSERT

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

Включено всі стовпці зі значень операції.

### UPDATE

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

Стовпці ідентичності (первинний ключ) виключаються з речення SET і використовуються в реченні WHERE. Значення для WHERE беруться шляхом декодування ключа рядка.

### DELETE

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

У реченні WHERE з’являються лише стовпці ідентичності.

### Захищене UPDATE

Захищені оновлення містять умови для ідентичності рядка та очікуваного поточного рядка.

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

Якщо рядок більше не має очікуваного значення `status`, оператор змінює нуль рядків, і захищене застосування відкочує транзакцію.

### Захищене INSERT

Захищені вставлення використовують `NOT EXISTS`, щоб не вставляти рядок з ідентичністю, яка з’явилася після створення плану.

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

## Обробка NULL

Значення NULL передаються як PHP `null` у масиві params. Адаптер з’єднання обробляє прив’язування:

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

У виводі пробного запуску значення NULL відображаються як літеральний рядок `NULL` без лапок.

## Визначення стовпців ідентичності

`SqlGenerator` визначає, які стовпці формують ідентичність рядка, за `TableSnapshot::identityColumns` базового знімка. Ці стовпці використовуються в реченнях WHERE для операторів UPDATE і DELETE та виключаються з речень SET для UPDATE.

Якщо базового знімка немає, як запасний варіант використовується перший стовпець у масиві значень. Якщо масив значень порожній, передбачається `id`.

## Передавання драйвера

Щоб керувати взяттям ідентифікаторів у лапки, передайте екземпляр `Driver`:

```php
use Merql\Driver\SqliteDriver;

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