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

### Автоматическое определение внешних ключей

`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.

Если базовый снимок отсутствует, в качестве запасного варианта используется первый столбец массива values. Если массив values пуст, предполагается `id`.

## Передача драйвера

Чтобы управлять заключением идентификаторов в кавычки, передайте экземпляр `Driver`:

```php
use Merql\Driver\SqliteDriver;

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