Lewati ke konten

Merge Tiga Arah

Merge tiga arah membandingkan dua kumpulan perubahan dengan leluhur yang sama, lalu menggabungkannya menjadi satu hasil. merql menerapkan algoritma ini pada baris database, bukan baris berkas.

PHP
use Merql\Merge\ThreeWayMerge;

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

echo $result->operationCount(); // resolved operations
echo $result->conflictCount();  // unresolved conflicts

Tiga keadaan

Setiap merge memerlukan tepat tiga snapshot:

  • Base -- keadaan database saat kedua cabang mulai berbeda.
  • Ours -- salah satu cabang setelah mengalami perubahan, misalnya salinan sandbox yang telah diedit.
  • Theirs -- cabang lainnya setelah mengalami perubahan, misalnya database aktif yang terus berjalan.

Ours dan theirs masing-masing harus diambil dari database yang menyimpan cabang tersebut. Aturan merge memperlakukan kedua sisi dengan cara yang sama; namanya menentukan source setiap operasi, penamaan jenis konflik (update_delete berarti ours diperbarui dan theirs dihapus), serta sisi yang diutamakan oleh ConflictPolicy::OursWins dan TheirsWins.

Kode
         Base (snapshot at fork time)
        /                              \
   Ours (our changes)                   Theirs (their changes)
        \                              /
         --------- MERGE ----------
                     |
              Merged result
              + conflicts (if any)

Tanpa base bersama, Anda tidak dapat membedakan "ditambahkan" dari "tidak berubah". Perbandingan dua arah menunjukkan bahwa sisi A memiliki nilai dan sisi B tidak, tetapi tidak dapat menentukan apakah A menambahkannya atau B menghapusnya. Base menjawab pertanyaan itu.

Cara changeset dihitung

Differ membandingkan setiap snapshot dengan base untuk menghasilkan dua changeset. Setiap changeset berisi tiga jenis operasi:

Base Saat ini Operasi
Baris ada Baris ada, fingerprint sama Tidak ada perubahan
Baris ada Baris ada, fingerprint berbeda UPDATE (perbedaan per kolom)
Baris ada Baris tidak ada DELETE
Baris tidak ada Baris ada INSERT

Fingerprint (hash SHA-256 dari konten baris yang dinormalisasi) menyediakan jalur cepat. Jika dua fingerprint cocok, baris tidak berubah dan perbandingan tingkat kolom tidak diperlukan.

PHP
use Merql\Diff\Differ;

$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);

Aturan merge

Kedua changeset kemudian digabungkan operasi demi operasi. Tabel ini menentukan hasil untuk setiap kombinasi:

Ours Theirs Hasil
Tidak ada perubahan Tidak ada perubahan Pertahankan base
Tidak ada perubahan Diperbarui Terima theirs
Diperbarui Tidak ada perubahan Terima ours
Diperbarui (nilai sama) Diperbarui (nilai sama) Terima (keduanya setuju)
Diperbarui (nilai berbeda) Diperbarui (nilai berbeda) Konflik
Tidak ada perubahan Dihapus Terima penghapusan
Dihapus Tidak ada perubahan Terima penghapusan
Dihapus Dihapus Terima penghapusan (keduanya setuju)
Diperbarui Dihapus Konflik
Dihapus Diperbarui Konflik
Disisipkan (hanya ours) -- Terima penyisipan
-- Disisipkan (hanya theirs) Terima penyisipan
Disisipkan (PK sama) Disisipkan (PK sama) Konflik

Saat kedua sisi memperbarui baris yang sama, merql tidak langsung menyatakan adanya konflik. Proses berlanjut ke penggabungan tingkat kolom dengan membandingkan setiap kolom secara terpisah. Lihat Penggabungan Tingkat Kolom untuk detailnya.

Menggunakan facade

Facade Merql bekerja pada snapshot bernama yang telah disimpan ke disk. Panggil Merql::init() dengan database yang menyimpan setiap keadaan sebelum mengambil snapshot:

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');
Merql::init($live);
Merql::snapshot('theirs');

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

Jangan mengambil ketiga snapshot secara berurutan dari satu database. Dengan begitu, theirs juga akan berisi perubahan ours, sehingga setiap baris yang disisipkan ours akan tampak disisipkan oleh kedua sisi dan dilaporkan sebagai konflik insert_insert.

Merql::merge(), Merql::patch(), dan Merql::plan() membuat ThreeWayMerge tanpa CellMergeConfig, sehingga hanya menggabungkan pada tingkat kolom. Untuk penggabungan tingkat sel, muat snapshot dan gunakan kelas tersebut secara langsung, seperti di bawah ini.

Menggunakan kelas secara langsung

Untuk kendali lebih lanjut, buat instans komponen sendiri. Snapshotter::capture() tidak menyimpan apa pun, jadi simpan snapshot di memori atau simpan dengan SnapshotStore::save():

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\SnapshotStore;

$base   = (new Snapshotter($live))->capture('base');
// ... copy live to the sandbox, then both databases change ...
$ours   = (new Snapshotter($sandbox))->capture('ours');
$theirs = (new Snapshotter($live))->capture('theirs');

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

// Snapshots saved earlier, merged with cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
$result = $merge->merge(
    SnapshotStore::load('base'),
    SnapshotStore::load('ours'),
    SnapshotStore::load('theirs'),
);

Pintasan patch

Jika hanya satu sisi yang memiliki perubahan (tanpa modifikasi bersamaan), gunakan patch(). Ini adalah merge dua arah dengan ours sama dengan base, jadi konflik tidak mungkin terjadi:

PHP
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.

Melalui facade:

PHP
$result = Merql::patch('base', 'changes');

patch() setara dengan merge($base, $base, $changes). Karena ours tidak pernah berbeda dari base, setiap perubahan dari theirs diterima tanpa konflik.

Deteksi ketidakcocokan skema

Jika kolom ditambahkan ke atau dihapus dari tabel di antara snapshot, merge tetap berjalan, tetapi mencatat ketidakcocokan tersebut. Jenis kolom yang berubah tidak dibandingkan, dan kolom yang diganti namanya muncul sebagai satu kolom yang dihapus dan satu yang ditambahkan; lihat Validasi Skema.

PHP
$result = $merge->merge($base, $ours, $theirs);

if ($result->hasSchemaMismatches()) {
    foreach ($result->schemaMismatches() as $mismatch) {
        echo $mismatch->getMessage() . "\n";
    }
}

merql tidak memigrasikan skema secara otomatis. Perubahan skema menjadi tanggung jawab pemanggil. Merge melaporkannya agar Anda dapat menentukan langkah selanjutnya.

MergeResult

Objek hasil membawa semua yang Anda perlukan:

PHP
$result->isClean();            // true if no conflicts
$result->operations();         // list of MergeOperation objects
$result->conflicts();          // list of Conflict objects
$result->operationCount();     // number of resolved operations
$result->conflictCount();      // number of unresolved conflicts
$result->hasSchemaMismatches(); // true if schemas differ
$result->baseSnapshot();       // the base snapshot used for the merge

Setiap MergeOperation memiliki jenis (insert, update, atau delete), nama tabel, kunci baris, nilai kolom, dan sumber (ours, theirs, atau merged). merged menandai baris yang diperbarui oleh kedua sisi (digabungkan kolom demi kolom) atau dihapus oleh kedua sisi.

Tempat hasil merge diterapkan

Hasil merge menjelaskan cara mengubah base menjadi keadaan gabungan. Operasinya membawa perubahan dari kedua sisi: penyisipan yang dibuat ours dan penyisipan yang dibuat theirs sama-sama muncul sebagai penyisipan.

  • Merql::apply() (atau Applier) menjalankan setiap operasi pada database yang terhubung. Operasi ini diterapkan dengan bersih ke database yang masih menyimpan base, misalnya salinan baru dari keadaan base. Jika diterapkan pada database yang sudah berisi perubahan dari salah satu sisi, penyisipan baris yang sudah ada akan gagal dan transaksi dibatalkan.
  • Untuk membawa perubahan ours ke database yang sudah menyimpan theirs, misalnya mempromosikan perubahan sandbox ke lingkungan aktif, terapkan hanya operasi yang belum dimiliki database tersebut: operasi dengan sumber ours dan pembaruan merged. Gunakan rencana merge untuk memilihnya dan Penerapan Terjaga untuk menulisnya dengan prasyarat baris aktif.