
# Penerapan Terlindungi

`GuardedApplier` hanya menerapkan `MergeResult` yang bersih jika database target masih cocok dengan snapshot aktif yang diharapkan. Ini berguna saat rencana penggabungan telah ditinjau sebelumnya dan baris aktif mungkin berubah sebelum penerapan.

Penyiapan umumnya mempromosikan suntingan sandbox ke database aktif. Base adalah database aktif saat sandbox disalin darinya, ours adalah sandbox yang telah diedit, dan theirs adalah database aktif saat ini. Targetnya adalah database aktif, jadi snapshot aktif yang diharapkan adalah `theirs`, dan operasi yang akan ditulis adalah yang belum ada di database aktif (lihat [Memilih operasi](#choosing-operations)).

```php
use Merql\Connection;
use Merql\Merql;
use Merql\Plan\OperationSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

Merql::init(Connection::sqlite('/data/live.sqlite'));

$plan = Merql::plan('promote-1', 'base', 'ours', 'theirs');

// Skip rows with unresolved conflicts.
$conflicted = [];
foreach ($plan->conflicts as $conflict) {
    $conflicted[$conflict->table . "\0" . $conflict->rowKey] = true;
}

$ids = [];
foreach ($plan->operations as $operation) {
    $missing = $operation->source === 'ours'
        || ($operation->source === 'merged' && $operation->type === 'update');

    if ($missing && !isset($conflicted[$operation->table . "\0" . $operation->rowKey])) {
        $ids[] = $operation->id;
    }
}

$selected = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    OperationSelection::fromIds($ids),
    SnapshotStore::load('base'),
);

$applied = Merql::applyGuarded($selected, SnapshotStore::load('theirs'));

if ($applied->hasErrors()) {
    foreach ($applied->errors() as $error) {
        echo $error . "\n";
    }
}
```

`Merql::applyGuarded()` menulis ke database yang diteruskan ke `Merql::init()`. Snapshot aktif yang diharapkan harus menggambarkan database yang sama: ambil dari target dan pastikan cukup baru agar prasyaratnya bermakna.

## Prasyarat

SQL menambahkan prasyarat baris aktif ke pernyataan yang dihasilkan:

- pembaruan mengharuskan identitas baris target ada dan nilai kolom non-identitasnya cocok dengan baris aktif yang diharapkan,
- penghapusan mengharuskan identitas baris target ada dan nilai kolom non-identitasnya cocok dengan baris aktif yang diharapkan,
- penyisipan mengharuskan identitas target belum ada.

Jika ada pernyataan yang memengaruhi nol baris, merql menganggap rencana sudah kedaluwarsa. Transaksi dibatalkan dan `ApplyResult` yang dikembalikan berisi kesalahan `Optimistic precondition failed for guarded merge statement.`

Pada MySQL, pembaruan yang nilainya sudah sama dengan baris aktif juga memengaruhi nol baris, karena MySQL menghitung baris yang berubah, bukan baris yang cocok. Pembaruan seperti itu gagal karena dianggap kedaluwarsa meskipun barisnya cocok ([#2](https://github.com/inline0/merql/issues/2)). SQLite menghitung baris yang cocok dan menerimanya. Ini dapat terjadi pada pembaruan `merged` ketika kedua sisi membuat perubahan yang sama pada suatu baris.

## Memilih operasi

Hasil penggabungan memuat operasi dari kedua sisi karena hasil itu mengubah base menjadi keadaan gabungan. Jika diterapkan ke target yang sudah memuat theirs, operasi yang berasal dari theirs menggambarkan perubahan yang sudah ada pada target, sehingga prasyaratnya gagal:

| Operasi | Keadaan pada target yang memuat theirs | Pilih |
|---|---|---|
| `ours` insert, update, atau delete | Tidak ada | Ya |
| `merged` update (kedua sisi mengubah baris) | Memiliki kolom theirs, tidak memiliki kolom ours | Ya |
| `theirs` insert | Baris sudah ada, sehingga insert yang dijaga memengaruhi nol baris | Tidak |
| `merged` delete (kedua sisi menghapus baris) | Baris sudah tidak ada, sehingga delete yang dijaga memengaruhi nol baris | Tidak |
| `theirs` update atau delete | Sudah diterapkan | Tidak |

Karena itu, meneruskan seluruh hasil `Merql::merge()` ke `applyGuarded()` akan gagal begitu theirs menyisipkan baris atau kedua sisi menghapus baris. Pilih operasi terlebih dahulu, berdasarkan sumber seperti di atas atau dengan `ChangeGroupSelection` yang dibuat dari UI tinjauan. Jangan sertakan baris yang tercantum di `$plan->conflicts`: hasil yang dipilih selalu bersih, dan operasi untuk baris yang berkonflik membawa nilai ours pada kolom yang berkonflik (lihat [Rencana Penggabungan](https://merql.inline0.dev/id/docs/merge/plans#selecting-what-a-target-is-missing)).

## Penerapan Pilihan

Penerapan dengan penjagaan berfungsi pada hasil penggabungan pilihan yang dibuat dari rencana penggabungan, termasuk pilihan yang dibuat berdasarkan kelompok perubahan.

```php
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

$selection = ChangeGroupSelection::fromIds([$plan->changeGroups[0]->id]);
$selected = (new SelectedMergeResultFactory())->fromChangeGroupSelection(
    $plan,
    $selection,
    SnapshotStore::load($plan->baseSnapshot),
);

$applied = Merql::applyGuarded($selected, SnapshotStore::load($plan->theirsSnapshot));
```

Ini memungkinkan aplikasi host menyiapkan subset kelompok yang telah ditinjau sekaligus memastikan baris aktif tidak berubah sejak rencana dibuat.

## Pratinjau SQL

Gunakan `SqlGenerator::generateGuarded()` untuk memeriksa pernyataan yang dijaga tanpa menjalankannya.

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

$statements = SqlGenerator::generateGuarded(
    $selected,
    SnapshotStore::load($plan->theirsSnapshot),
);

foreach ($statements as $statement) {
    echo $statement['sql'] . "\n";
}
```

Pernyataan yang dihasilkan tetap menggunakan parameter. Prasyarat dinyatakan dengan klausa `WHERE` tambahan untuk update/delete dan pemeriksaan `NOT EXISTS` untuk insert.

## Penerapan dengan penjagaan atau biasa

`Merql::apply()` menjalankan operasi yang sama tanpa prasyarat. Update dan delete kemudian berhasil terlepas dari apakah baris aktif berubah sejak rencana dibuat, sedangkan insert untuk baris yang sudah ada gagal dengan kesalahan kunci duplikat dari database. Gunakan penerapan biasa untuk target yang Anda ketahui masih memuat base, dan penerapan dengan penjagaan ketika penulis lain mungkin telah mengubah target sejak snapshot dibuat.
