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

## 문 형식

각 문은 두 키가 있는 연관 배열입니다:

```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 값은 params 배열에서 PHP `null`로 전달됩니다. 바인딩은 연결 어댑터가 처리합니다.

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

드라이 런 출력에서 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"
```
