
# 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'),
);
```

## 文の形式

各文は、次の2つのキーを持つ連想配列です:

```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`を持たなくなった場合、文の影響行数は0となり、ガード付き適用はトランザクションをロールバックします。

### ガード付き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
```

dry runの出力では、NULL値は引用符で囲まないリテラル文字列`NULL`として出力されます。

## 識別列の検出

`SqlGenerator`は、ベーススナップショットの`TableSnapshot::identityColumns`を調べて、どの列が行の識別情報を構成するか判断します。これらの列はUPDATE文とDELETE文のWHERE句で使われ、UPDATE文のSET句からは除外されます。

ベーススナップショットを利用できない場合、値の配列の最初の列が代替として使われます。値の配列が空の場合は、`id`とみなされます。

## ドライバーの指定

識別子の引用符付け形式を指定するには、`Driver`インスタンスを渡します:

```php
use Merql\Driver\SqliteDriver;

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