
# Generarea SQL

`SqlGenerator` convertește un `MergeResult` în instrucțiuni SQL parametrizate. Toate valorile sunt transmise ca parametri, niciodată interpolate în șirul 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']
}
```

Folosiți `generateGuarded()` când un plan de îmbinare revizuit ar trebui aplicat doar dacă rândurile live corespund în continuare unui instantaneu așteptat.

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

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

## Formatul instrucțiunilor

Fiecare instrucțiune este un tablou asociativ cu două chei:

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

Identificatorii (numele tabelelor și coloanelor) sunt citați folosind convenția driverului bazei de date. MySQL folosește backtick-uri, iar SQLite folosește ghilimele duble. Dacă nu este furnizat niciun driver, citarea cu backtick-uri este folosită implicit.

## Ordonarea operațiilor

1. Mai întâi **INSERTs** -- rândurile noi trebuie să existe înainte ca actualizările să le poată referi.
2. Apoi **UPDATEs**.
3. La final **DELETEs** -- referințele către rânduri trebuie eliminate înainte de ștergere.

1. **INSERĂRILE** mai întâi -- rândurile noi trebuie să existe înainte ca actualizările să le poată referi.
2. **ACTUALIZĂRILE** apoi.
3. **ȘTERGERILE** la final -- referințele către rânduri trebuie eliminate înainte de ștergere.

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

## Ordonarea cheilor străine

Când sunt furnizate dependențele dintre cheile străine, operațiile din fiecare grup sunt ordonate topologic. Tabelele părinte sunt procesate înaintea tabelelor copil pentru inserări și actualizări. Tabelele copil sunt procesate înaintea tabelelor părinte pentru ștergeri.

```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` efectuează o sortare topologică a grafului dependențelor. Dependențele circulare sunt detectate și întrerupte pentru a preveni buclele infinite.

### Detectarea automată a cheilor străine

`Applier` citește automat relațiile cheilor străine din baza de date:

```php
use Merql\Apply\Applier;

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

## Exemple generate de SQL

### INSERT

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

Coloanele de identitate (cheia primară) sunt excluse din clauza SET și folosite în clauza WHERE. Valorile clauzei WHERE provin din decodificarea cheii rândului.

### UPDATE

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

Doar coloanele de identitate apar în clauza WHERE.

### DELETE

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

Doar coloanele de identitate apar în clauza WHERE.

### ACTUALIZARE cu protecție

Actualizările cu protecție includ atât clauze de identitate, cât și clauze pentru rândul live așteptat.

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

Dacă rândul nu mai are identitatea așteptată `status`, instrucțiunea nu afectează niciun rând, iar aplicarea cu protecție anulează tranzacția.

### INSERARE cu protecție

Inserările cu protecție folosesc `NOT EXISTS` pentru a evita inserarea într-o identitate apărută după crearea planului.

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

## Gestionarea valorilor NULL

Valorile NULL sunt transmise ca PHP `null` în tabloul params. Adaptorul conexiunii gestionează legarea:

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

În rezultatul rulării de probă, valorile NULL sunt redate ca șirul literal `NULL` fără ghilimele.

## Detectarea coloanelor de identitate

`SqlGenerator` determină coloanele care alcătuiesc identitatea rândului, examinând `TableSnapshot::identityColumns` din instantaneul de bază. Aceste coloane sunt folosite în clauzele WHERE pentru instrucțiunile UPDATE și DELETE și sunt excluse din clauzele SET ale instrucțiunilor UPDATE.

Dacă nu este disponibil niciun instantaneu de bază, prima coloană din tabloul de valori este folosită ca soluție de rezervă. Dacă tabloul de valori este gol, se presupune `id`.

## Transmiterea unui driver

Pentru a controla citarea identificatorilor, transmiteți o instanță `Driver`:

```php
use Merql\Driver\SqliteDriver;

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