
# SQL-generering

`SqlGenerator` konverterer en `MergeResult` til parameteriserede SQL-sætninger. Alle værdier bindes som parametre og indsættes aldrig direkte i SQL-strengen.

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

Brug `generateGuarded()`, når en gennemgået fletningsplan kun skal anvendes, hvis live-rækkerne stadig svarer til et forventet øjebliksbillede.

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

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

## Sætningsformat

Hver sætning er et associativt array med to nøgler:

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

Identifikatorer (tabel- og kolonnenavne) citeres med databasens drivers citeringsstil. MySQL bruger backticks, SQLite bruger dobbelte anførselstegn. Hvis der ikke angives nogen driver, bruges backticks som standard.

## Rækkefølge af handlinger

Handlinger grupperes efter type og udføres i denne rækkefølge:

1. **INSERTs** først -- nye rækker skal eksistere, før opdateringer kan referere til dem.
2. **UPDATEs** derefter.
3. **DELETEs** til sidst -- referencer til rækker skal fjernes, før de slettes.

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

## Rækkefølge efter fremmednøgler

Når der angives afhængigheder mellem fremmednøgler, sorteres handlingerne i hver gruppe topologisk. Overordnede tabeller behandles før underordnede ved indsættelser og opdateringer. Underordnede tabeller behandles før overordnede ved sletninger.

```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` udfører en topologisk sortering af afhængighedsgrafen. Cirkulære afhængigheder registreres og brydes for at forhindre uendelige løkker.

### Automatisk registrering af fremmednøgler

`Applier` henter automatisk relationer mellem fremmednøgler fra databasen:

```php
use Merql\Apply\Applier;

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

## Eksempler på genereret SQL

### INSERT

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

Alle kolonner fra handlingens værdier medtages.

### UPDATE

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

Identitetskolonner (primærnøgle) udelades fra SET-delen og bruges i WHERE-delen. Værdierne i WHERE-delen kommer fra afkodning af rækkenøglen.

### DELETE

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

Kun identitetskolonner indgår i WHERE-delen.

### Beskyttet UPDATE

Beskyttede opdateringer indeholder både identitetsbetingelser og betingelser for den forventede live-række.

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

Hvis rækken ikke længere har den forventede `status`, påvirker sætningen nul rækker, og beskyttet anvendelse ruller transaktionen tilbage.

### Beskyttet INSERT

Beskyttede indsættelser bruger `NOT EXISTS` for at undgå at indsætte en identitet, der er dukket op, siden planen blev oprettet.

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

## Håndtering af NULL

NULL-værdier videregives som PHP `null` i params-arrayet. Forbindelsesadapteren håndterer bindingen:

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

I output fra en prøvekørsel vises NULL-værdier som den bogstavelige streng `NULL` uden anførselstegn.

## Registrering af identitetskolonner

`SqlGenerator` afgør, hvilke kolonner der udgør rækkeidentiteten, ved at se på `TableSnapshot::identityColumns` i grundøjebliksbilledet. Disse kolonner bruges i WHERE-betingelser for UPDATE- og DELETE-sætninger og udelades fra SET-betingelser i UPDATEs.

Hvis der ikke er et grundøjebliksbillede til rådighed, bruges den første kolonne i værdiarrayet som reserve. Hvis værdiarrayet er tomt, antages `id`.

## Angivelse af en driver

Hvis du vil styre citeringen af identifikatorer, skal du angive en `Driver`-instans:

```php
use Merql\Driver\SqliteDriver;

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