
# SQL-luonti

`SqlGenerator` muuntaa `MergeResult`-tuloksen parametrisoiduiksi SQL-lausekkeiksi. Kaikki arvot sidotaan parametreiksi; niitä ei koskaan sijoiteta suoraan SQL-merkkijonoon.

```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']
}
```

Käytä toimintoa `generateGuarded()`, kun tarkistettu yhdistämissuunnitelma tulisi ottaa käyttöön vain, jos reaaliaikaiset rivit vastaavat edelleen odotettua tilannevedosta.

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

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

## Lausekemuoto

Jokainen lauseke on assosiatiivinen taulukko, jossa on kaksi avainta:

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

Tunnisteet (taulujen ja sarakkeiden nimet) lainataan tietokanta-ajurin tyylin mukaisesti. MySQL käyttää kenoviivoja, SQLite käyttää kaksoislainausmerkkejä. Jos ajuria ei anneta, oletuksena käytetään kenoviivoja.

## Operaatioiden järjestys

Operaatiot ryhmitellään tyypin mukaan ja suoritetaan tässä järjestyksessä:

1. **INSERTit** ensin -- uusien rivien on oltava olemassa, ennen kuin niihin voidaan viitata päivityksissä.
2. **UPDATEt** toisena.
3. **DELETEt** viimeisenä -- viittaukset riveihin on poistettava ennen niiden poistamista.

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

## Viiteavainten mukainen järjestys

Kun viiteavainriippuvuudet annetaan, kunkin ryhmän operaatiot järjestetään topologisesti. Lisäyksissä ja päivityksissä ylätaulut käsitellään ennen alitauluja. Poistoissa alitaulut käsitellään ennen ylätauluja.

```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` lajittelee riippuvuusverkon topologisesti. Kehämäiset riippuvuudet tunnistetaan ja puretaan loputtomien silmukoiden estämiseksi.

### Viiteavainten automaattinen tunnistus

`Applier` lukee viiteavainsuhteet automaattisesti tietokannasta:

```php
use Merql\Apply\Applier;

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

## Luotujen SQL-lausekkeiden esimerkkejä

### INSERT

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

Kaikki operaation arvoissa olevat sarakkeet sisällytetään.

### UPDATE

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

Identiteettisarakkeet (pääavain) jätetään pois SET-lausekkeesta ja niitä käytetään WHERE-lausekkeessa. WHERE-lausekkeen arvot saadaan purkamalla rivin avain.

### DELETE

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

WHERE-lausekkeessa ovat vain identiteettisarakkeet.

### Suojattu UPDATE

Suojatut päivitykset sisältävät sekä identiteettiä että odotettua reaaliaikaista riviä koskevat ehdot.

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

Jos rivillä ei enää ole odotettua arvoa `status`, lauseke vaikuttaa nollaan riviin ja suojattu käyttöönotto peruu tapahtuman.

### Suojattu INSERT

Suojatuissa lisäyksissä käytetään `NOT EXISTS`-toimintoa estämään lisäys identiteettiin, joka on tullut käyttöön suunnitelman luonnin jälkeen.

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

## NULL-arvojen käsittely

NULL-arvot välitetään muodossa PHP `null` params-taulukossa. Yhteysadapteri huolehtii sidonnasta:

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

Koeajon tulosteessa NULL-arvot näytetään merkkijonoliteraalina `NULL` ilman lainausmerkkejä.

## Identiteettisarakkeiden tunnistus

`SqlGenerator` määrittää rivin identiteetin muodostavat sarakkeet pohjan tilannevedoksen `TableSnapshot::identityColumns`-arvon perusteella. Näitä sarakkeita käytetään UPDATE- ja DELETE-lausekkeiden WHERE-osissa, ja ne jätetään pois UPDATE-lauseiden SET-osista.

Jos pohjan tilannevedosta ei ole saatavilla, varasarake otetaan values-taulukon ensimmäisestä sarakkeesta. Jos values-taulukko on tyhjä, oletetaan `id`.

## Ajurin välittäminen

Tunnisteiden lainausmerkkien hallintaan välitä `Driver`-instanssi:

```php
use Merql\Driver\SqliteDriver;

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