
# Memulai

Panduan ini menjelaskan instalasi, koneksi ke database, pengambilan snapshot, penghitungan perbedaan, dan pelaksanaan penggabungan tiga arah.

## Instalasi

```bash
composer require merql/merql
```

merql memerlukan PHP 8.2+ dengan `ext-mbstring` dan `ext-zlib` (keduanya diwajibkan oleh dependensi `pitmaster/pitmaster`). Anda juga memerlukan ekstensi untuk adapter yang digunakan: `ext-pdo` dengan `ext-pdo_sqlite` atau `ext-pdo_mysql` untuk adapter PDO, atau `ext-mysqli` untuk adapter mysqli. Composer hanya menyarankan ekstensi tersebut, jadi pastikan ekstensi yang diperlukan sudah terpasang.

Paket ini memasang CLI sebagai `vendor/bin/merql`. Gunakan merql 0.4.1 atau versi lebih baru untuk CLI; pada rilis sebelumnya, biner tidak dapat menemukan autoloader ketika merql dipasang sebagai dependensi.

## Menghubungkan ke database

merql bekerja melalui antarmuka adapter koneksi yang sederhana. Adapter bawaan tersedia untuk PDO dan mysqli, dengan driver dialek bawaan untuk MySQL dan SQLite.

```php
use Merql\Connection;

// SQLite (in-memory or file-based, through PDO).
$connection = Connection::sqlite(':memory:');
$connection = Connection::sqlite('/path/to/database.sqlite');

// MySQL through PDO.
$connection = Connection::mysql('127.0.0.1', 'my_database', 'root', 'password');

// MySQL through mysqli.
$connection = Connection::mysqli('127.0.0.1', 'my_database', 'root', 'password');

// Any MySQL or SQLite PDO DSN.
$connection = Connection::fromDsn('mysql:host=127.0.0.1;dbname=my_database;charset=utf8mb4', 'root', 'password');
```

`fromDsn()` membuka DSN apa pun yang didukung PDO, tetapi merql hanya menyertakan driver dialek untuk MySQL dan SQLite. Dengan mesin lain seperti `pgsql:`, koneksi berhasil dibuka tetapi `Merql::init()` menghasilkan `Unsupported database driver: pgsql` sampai Anda mendaftarkan driver untuk mesin tersebut. Lihat [Driver Database](https://merql.inline0.dev/id/docs/apply/drivers).

## Mengambil snapshot

Snapshot mencatat setiap baris dari setiap tabel (atau subset tabel) sebagai sidik jari dan data. Sidik jari memungkinkan deteksi perubahan dengan cepat; data baris lengkap tersedia saat perbedaan diperlukan.

```php
use Merql\Merql;

Merql::init($connection);

// Snapshot all tables.
$snapshot = Merql::snapshot('baseline');

// Snapshot specific tables.
$snapshot = Merql::snapshot('baseline', ['posts', 'comments', 'users']);
```

Secara default, snapshot disimpan sebagai berkas JSON di `.merql/snapshots/`. Anda dapat mengubah lokasi ini:

```php
use Merql\Snapshot\SnapshotStore;

SnapshotStore::setDirectory('/var/data/merql-snapshots');
```

## Menghitung perbedaan

Perbandingan membandingkan dua snapshot dan menghasilkan set perubahan yang terdiri dari insert, update, dan delete. Kedua snapshot harus sudah ada; di sini `current` adalah snapshot kedua yang diambil setelah database berubah.

```php
$changeset = Merql::diff('baseline', 'current');

echo "Inserts: " . count($changeset->inserts()) . "\n";
echo "Updates: " . count($changeset->updates()) . "\n";
echo "Deletes: " . count($changeset->deletes()) . "\n";

// Inspect changes for a single table.
$postChanges = $changeset->forTable('posts');

// Check per-column diffs on updates.
foreach ($changeset->updates() as $update) {
    echo "Row {$update->rowKey} in {$update->table}:\n";
    foreach ($update->columnDiffs as $diff) {
        echo "  {$diff->column}: {$diff->oldValue} -> {$diff->newValue}\n";
    }
}
```

## Penggabungan tiga arah

Operasi inti. Mengambil tiga snapshot bernama (base, ours, theirs) dan menggabungkan kedua set perubahan.

Base adalah keadaan awal kedua salinan. Ours dan theirs adalah dua salinan setelah berubah secara terpisah, jadi masing-masing harus diambil dari database yang memuatnya. Contohnya, ketika sandbox disalin dari database aktif:

```php
use Merql\Connection;
use Merql\Merql;

$live    = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');

Merql::init($live);
Merql::snapshot('base');      // before the sandbox is copied from live

// ... copy live.sqlite to sandbox.sqlite, then both databases change ...

Merql::init($sandbox);
Merql::snapshot('ours');      // the sandbox with our edits
Merql::init($live);
Merql::snapshot('theirs');    // live with its own changes

$result = Merql::merge('base', 'ours', 'theirs');

echo "Operations: {$result->operationCount()}\n";
echo "Conflicts:  {$result->conflictCount()}\n";
echo "Clean:      " . ($result->isClean() ? 'yes' : 'no') . "\n";
```

Jangan mengambil ketiga snapshot secara berurutan dari satu database. Theirs akan sudah memuat perubahan ours, sehingga setiap baris yang disisipkan ours akan muncul sebagai konflik `insert_insert`.

`Merql::merge()` menggabungkan pada tingkat kolom. Untuk juga menggabungkan di dalam nilai TEXT dan JSON, gunakan `ThreeWayMerge` dengan `CellMergeConfig`; lihat [Penggabungan Tingkat Sel](https://merql.inline0.dev/id/docs/merge/cell-level).

### Menangani konflik

Ketika kedua sisi mengubah kolom yang sama menjadi nilai yang berbeda, merql menimbulkan konflik. Anda dapat menyelesaikan konflik secara otomatis atau memeriksanya secara manual.

```php
use Merql\Merge\ConflictPolicy;
use Merql\Merge\ConflictResolver;

if (!$result->isClean()) {
    // Auto-resolve: one side wins.
    $resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

    // Or inspect each conflict.
    foreach ($result->conflicts() as $conflict) {
        echo "Conflict on {$conflict->table()} row {$conflict->rowKey()}:\n";
        echo "  Type:   {$conflict->type()}\n";
        echo "  Column: {$conflict->column()}\n";
        echo "  Ours:   " . var_export($conflict->oursValue(), true) . "\n";
        echo "  Theirs: " . var_export($conflict->theirsValue(), true) . "\n";
    }
}
```

## Menerapkan penggabungan

Setelah Anda mendapatkan hasil penggabungan yang bersih (tanpa konflik yang belum terselesaikan), terapkan hasil tersebut ke database yang diteruskan ke `Merql::init()`.

Operasi dalam hasil penggabungan mengubah base menjadi keadaan gabungan, dan mencakup perubahan dari kedua sisi. `Merql::apply()` menjalankan semuanya, sehingga hasilnya dapat diterapkan dengan bersih ke database yang masih menyimpan keadaan base. Jika diterapkan ke database yang sudah berisi perubahan dari salah satu sisi, setiap penyisipan dari sisi tersebut akan mengenai baris yang sudah ada, gagal, dan membatalkan seluruh penggabungan. Untuk menerapkan perubahan dari satu sisi ke database yang sudah berisi perubahan dari sisi lain (misalnya, perubahan sandbox ke live), terapkan hanya operasi yang belum ada dengan [Terapkan dengan Pengamanan](https://merql.inline0.dev/id/docs/apply/guarded).

```php
$applied = Merql::apply($result);

echo "Rows affected: {$applied->rowsAffected()}\n";

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

Pelaksana membungkus semua operasi dalam sebuah transaksi. Jika ada pernyataan yang gagal, seluruh penggabungan dibatalkan dan kesalahan dikembalikan di `errors()`. Dalam kasus itu, `rowsAffected()` tetap menghitung pernyataan yang dijalankan sebelum kegagalan, meskipun semuanya telah dibatalkan ([#4](https://github.com/inline0/merql/issues/4)); periksa `hasErrors()` terlebih dahulu.

## Uji coba

Pratinjau SQL yang akan dihasilkan oleh penggabungan tanpa menjalankannya.

```php
use Merql\Apply\DryRun;

$statements = DryRun::generate($result);

foreach ($statements as $sql) {
    echo "{$sql};\n";
}
```

## Patch dua arah

Jika Anda hanya memiliki base dan serangkaian perubahan (tanpa modifikasi bersamaan pada "ours"), gunakan `patch()`. Ini setara dengan penggabungan ketika ours tidak berubah dari base.

```php
$result = Merql::patch('base', 'changes');
// Always clean: no concurrent edits means no conflicts.
Merql::apply($result);
```

## Menggunakan CLI

Semua operasi ini juga tersedia melalui baris perintah. Lihat [CLI Referensi](https://merql.inline0.dev/id/docs/cli) untuk detail lengkap.

```bash
export MERQL_DB_DSN="sqlite:mydb.sqlite"

vendor/bin/merql snapshot base
# ... change the database ...
vendor/bin/merql snapshot current
vendor/bin/merql diff base current
```

Untuk penggabungan tiga arah, ambil setiap snapshot dari database yang menyimpan keadaan tersebut, lalu gabungkan:

```bash
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base
MERQL_DB_DSN="sqlite:/data/sandbox.sqlite" vendor/bin/merql snapshot ours
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot theirs
vendor/bin/merql merge base ours theirs --dry-run
```

## Memfilter tabel dan kolom

Snapshot dapat mengecualikan tabel (seperti tabel cache atau sesi) dan mengabaikan kolom yang berubah-ubah (seperti stempel waktu `updated_at`).

```php
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

$snapshot = $snapshotter->capture('baseline', filters: [
    TableFilter::exclude(['sessions', 'cache_*']),
    ColumnFilter::ignore(['updated_at', 'modified_date']),
]);
```

## Langkah berikutnya

- [CLI Referensi](https://merql.inline0.dev/id/docs/cli) untuk antarmuka baris perintah lengkap.
- [PHP API Referensi](https://merql.inline0.dev/id/docs/api) untuk dokumentasi terperinci setiap kelas dan metode.
