
# Pembuatan SQL

`SqlGenerator` mengubah `MergeResult` menjadi pernyataan SQL berparameter. Semua nilai diikat sebagai parameter, tidak pernah disisipkan ke dalam string 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']
}
```

Gunakan `generateGuarded()` ketika rencana penggabungan yang telah ditinjau hanya boleh diterapkan jika baris aktif masih cocok dengan snapshot yang diharapkan.

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

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

## Format pernyataan

Setiap pernyataan berupa array asosiatif dengan dua kunci:

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

Identifier (nama tabel dan kolom) diberi tanda kutip menggunakan gaya pengutipan driver database. MySQL menggunakan backtick, sedangkan SQLite menggunakan tanda kutip ganda. Jika driver tidak diberikan, backtick digunakan secara default.

## Urutan operasi

Operasi dikelompokkan berdasarkan jenis dan dijalankan dengan urutan berikut:

1. **INSERT** terlebih dahulu -- baris baru harus ada sebelum dapat dirujuk oleh pembaruan.
2. **UPDATE** berikutnya.
3. **DELETE** terakhir -- referensi ke baris harus dihapus sebelum penghapusan.

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

## Urutan foreign key

Jika dependensi foreign key diberikan, operasi dalam setiap kelompok diurutkan secara topologis. Tabel induk diproses sebelum tabel anak untuk insert dan update. Tabel anak diproses sebelum tabel induk untuk delete.

```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` melakukan pengurutan topologis pada graf dependensi. Dependensi melingkar dideteksi dan diputus untuk mencegah loop tanpa akhir.

### Deteksi FK otomatis

`Applier` membaca relasi foreign key dari database secara otomatis:

```php
use Merql\Apply\Applier;

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

## Contoh SQL yang dihasilkan

### INSERT

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

Semua kolom dari nilai operasi disertakan.

### UPDATE

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

Kolom identitas (kunci utama) tidak disertakan dalam klausa SET dan digunakan dalam klausa WHERE. Nilai klausa WHERE berasal dari hasil dekode kunci baris.

### DELETE

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

Hanya kolom identitas yang muncul dalam klausa WHERE.

### UPDATE dengan penjagaan

Update dengan penjagaan menyertakan klausa identitas dan klausa baris aktif yang diharapkan.

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

Jika baris tidak lagi memiliki `status` yang diharapkan, pernyataan memengaruhi nol baris dan penerapan dengan penjagaan membatalkan transaksi.

### INSERT dengan penjagaan

Insert dengan penjagaan menggunakan `NOT EXISTS` untuk mencegah penyisipan pada identitas yang muncul sejak rencana dibuat.

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

## Penanganan NULL

Nilai NULL diteruskan sebagai PHP `null` dalam array params. Adapter koneksi menangani pengikatan:

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

Dalam keluaran uji kering, nilai NULL ditampilkan sebagai string literal `NULL` tanpa tanda kutip.

## Deteksi kolom identitas

`SqlGenerator` menentukan kolom yang membentuk identitas baris dengan melihat `TableSnapshot::identityColumns` dari snapshot base. Kolom ini digunakan dalam klausa WHERE pada pernyataan UPDATE dan DELETE, serta dikecualikan dari klausa SET pada UPDATE.

Jika snapshot base tidak tersedia, kolom pertama dalam array values digunakan sebagai cadangan. Jika array values kosong, `id` diasumsikan.

## Meneruskan driver

Untuk mengontrol pengutipan identifier, teruskan instance `Driver`:

```php
use Merql\Driver\SqliteDriver;

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