
# Geração de SQL

`SqlGenerator` converte um `MergeResult` em instruções SQL parametrizadas. Todos os valores são vinculados como parâmetros, nunca interpolados na string 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']
}
```

Use `generateGuarded()` quando um plano de mesclagem revisado só deva ser aplicado se as
linhas ativas ainda corresponderem a um snapshot esperado.

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

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

## Formato das instruções

Cada instrução é um array associativo com duas chaves:

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

Os identificadores (nomes de tabelas e colunas) são citados usando o estilo de citação do driver do banco de dados. MySQL usa aspas invertidas; SQLite usa aspas duplas. Quando nenhum driver é fornecido, as aspas invertidas são usadas por padrão.

## Ordenação das operações

As operações são agrupadas por tipo e executadas nesta ordem:

1. **INSERTs** primeiro -- as novas linhas devem existir antes que atualizações possam fazer referência a elas.
2. **UPDATEs** em seguida.
3. **DELETEs** por último -- as referências às linhas devem ser removidas antes da exclusão.

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

## Ordenação por chave estrangeira

Quando as dependências de chaves estrangeiras são fornecidas, as operações de cada grupo são ordenadas topologicamente. As tabelas-pai são processadas antes das tabelas-filhas nas inserções e atualizações. As tabelas-filhas são processadas antes das tabelas-pai nas exclusões.

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

O `ForeignKeyResolver` realiza uma ordenação topológica no grafo de dependências. Dependências circulares são detectadas e desfeitas para evitar loops infinitos.

### Detecção automática de chaves estrangeiras

O `Applier` lê automaticamente as relações de chaves estrangeiras do banco de dados:

```php
use Merql\Apply\Applier;

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

## Exemplos de SQL gerado

### INSERT

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

Todas as colunas dos valores da operação são incluídas.

### UPDATE

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

As colunas de identidade (chave primária) são excluídas da cláusula SET e usadas na cláusula WHERE. Os valores da cláusula WHERE vêm da decodificação da chave da linha.

### DELETE

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

Somente colunas de identidade aparecem na cláusula WHERE.

### UPDATE protegido

As atualizações protegidas incluem cláusulas de identidade e cláusulas esperadas para a linha ativa.

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

Se a linha não tiver mais o `status` esperado, a instrução afetará zero linhas
e a aplicação protegida reverterá a transação.

### INSERT protegido

As inserções protegidas usam `NOT EXISTS` para evitar inserir uma identidade que tenha
surgido desde a criação do plano.

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

## Tratamento de NULL

Os valores NULL são passados como PHP `null` no array params. O adaptador de conexão trata a vinculação:

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

Na saída de execução de teste, os valores NULL são exibidos como a string literal `NULL` sem aspas.

## Detecção de colunas de identidade

`SqlGenerator` determina quais colunas formam a identidade da linha consultando `TableSnapshot::identityColumns` do snapshot base. Essas colunas são usadas em cláusulas WHERE de instruções UPDATE e DELETE e excluídas das cláusulas SET de UPDATEs.

Se nenhum snapshot base estiver disponível, a primeira coluna do array values será usada como alternativa. Se o array values estiver vazio, assume-se `id`.

## Passando um driver

Para controlar a citação de identificadores, passe uma instância de `Driver`:

```php
use Merql\Driver\SqliteDriver;

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