
# 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. Първо **INSERT** — новите редове трябва да съществуват, преди актуализациите да могат да се позовават на тях.
2. След това **UPDATE**.
3. Накрая **DELETE** — връзките към редовете трябва да бъдат премахнати, преди да бъдат изтрити.

```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` в масива с параметри. Адаптерът за връзка обработва обвързването:

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