Identitas Baris
Identitas baris menentukan cara merql mencocokkan baris di antara snapshot. Saat membandingkan base dengan ours, sistem perlu mengetahui bahwa baris X di base adalah baris logis yang sama dengan baris Y di ours, meskipun beberapa nilai kolom berubah. Aturan identitas menentukan kolom yang membentuk kunci baris.
use Merql\Identity\PrimaryKeyIdentity;
$identity = new PrimaryKeyIdentity(['id']);
$key = $identity->key(['id' => 42, 'title' => 'Hello']);
// "42"
Antarmuka RowIdentity
Semua strategi identitas mengimplementasikan dua metode:
interface RowIdentity
{
public function key(array $row): string;
public function columns(): array;
}
key() membangun pengenal string unik dari data baris. columns() mengembalikan nama kolom yang digunakan untuk identitas.
PrimaryKeyIdentity
Strategi default. Mencocokkan baris berdasarkan kolom kunci utamanya.
use Merql\Identity\PrimaryKeyIdentity;
// Single-column PK.
$identity = new PrimaryKeyIdentity(['id']);
$key = $identity->key(['id' => 42, 'title' => 'Hello']);
// "42"
// Composite PK.
$identity = new PrimaryKeyIdentity(['tenant_id', 'user_id']);
$key = $identity->key(['tenant_id' => 1, 'user_id' => 99, 'name' => 'Alice']);
// "1\x1F99" (unit separator between parts)
Inilah strategi yang digunakan merql secara default ketika tabel memiliki kunci utama yang ditetapkan dalam skemanya.
NaturalKeyIdentity
Untuk tabel tanpa kunci utama auto-increment, mencocokkan baris berdasarkan kolom batasan unik. Berguna ketika PK berupa UUID atau ketika tabel menggunakan batasan unik gabungan sebagai identitas logisnya.
use Merql\Identity\NaturalKeyIdentity;
$identity = new NaturalKeyIdentity(['email']);
$key = $identity->key(['id' => 1, 'email' => 'alice@example.com', 'name' => 'Alice']);
// "alice@example.com"
$identity = new NaturalKeyIdentity(['country', 'postal_code']);
$key = $identity->key(['country' => 'US', 'postal_code' => '90210', 'city' => 'Beverly Hills']);
// "US\x1F90210"
ContentHashIdentity
Fallback untuk tabel tanpa kunci utama dan tanpa batasan unik. Menghitung hash SHA-256 dari semua nilai kolom. Dua baris dianggap sama jika setiap kolom cocok.
use Merql\Identity\ContentHashIdentity;
$identity = new ContentHashIdentity(['name', 'value']);
$key = $identity->key(['name' => 'color', 'value' => 'blue']);
// "a3f2..." (SHA-256 hash)
Identitas hash konten memiliki keterbatasan: jika konten suatu baris berubah, hash-nya juga berubah. Akibatnya, baris tersebut tampak sebagai penghapusan hash lama dan penyisipan hash baru, bukan pembaruan. Ini adalah konsekuensi bawaan ketika tidak ada kolom identitas yang stabil.
IdentityRule
IdentityRule adalah konfigurasi identitas yang dapat diserialisasi dan digunakan oleh pengambilan snapshot
serta rencana merge.
use Merql\Identity\IdentityRule;
$rule = IdentityRule::natural(['option_name']);
$key = $rule->key(['option_name' => 'siteurl']);
// "siteurl"
Jenis aturan:
| Jenis | Makna |
|---|---|
primary |
Kolom kunci utama database |
natural |
Kolom bisnis unik yang stabil |
composite |
Kolom identitas logis gabungan |
content |
Fallback hash konten |
IdentityRule::forSchema($schema) menentukan aturan default:
- Jika tabel memiliki kunci utama, gunakan kolom kunci utama.
- Jika tabel memiliki kunci unik, gunakan kolom dari kunci unik pertama.
- Jika keduanya tidak ada, gunakan semua kolom (fallback hash konten).
IdentityRuleSet
Gunakan IdentityRuleSet ketika aplikasi host mengetahui aturan identitas yang lebih baik daripada default skema.
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;
use Merql\Snapshot\Snapshotter;
$rules = new IdentityRuleSet([
'wp_options' => IdentityRule::natural(['option_name']),
'wp_term_relationships' => IdentityRule::composite(['object_id', 'term_taxonomy_id']),
]);
$snapshotter = new Snapshotter($connection, identityRules: $rules);
$snapshot = $snapshotter->capture('live', ['wp_options', 'wp_term_relationships']);
Snapshotter menentukan IdentityRule untuk setiap tabel yang diambil. Kolom yang dipilih disimpan di TableSnapshot::identityColumns dan digunakan untuk semua operasi diff, merge, plan, SQL, dan rollback berikutnya.
Merql::init() dapat menerima kumpulan aturan yang sama saat menggunakan facade statis:
use Merql\Merql;
Merql::init($connection, identityRules: $rules);
Merql::snapshot('live', ['wp_options']);
Kumpulan aturan memiliki serialisasi array yang stabil:
$payload = $rules->toArray();
$rules = IdentityRuleSet::fromArray($payload);
Identitas Ambigu
Jika dua baris yang diambil menghasilkan kunci identitas yang sama, pengambilan gagal alih-alih menimpa satu baris dengan baris lainnya.
use Merql\Exceptions\SnapshotException;
try {
$snapshotter->capture('live', ['wp_postmeta']);
} catch (SnapshotException $e) {
// The configured identity rule matched multiple rows.
}
Aplikasi host dapat melakukan pemeriksaan awal pada baris mentah dengan IdentityRuleSet::conflictsFor() saat ingin melaporkan ambiguitas sebelum pengambilan:
$conflicts = $rules->conflictsFor(
'wp_postmeta',
IdentityRule::natural(['post_id', 'meta_key']),
$rows,
);
Untuk tabel meta WordPress, post_id + meta_key biasanya berguna untuk pengelompokan dan tampilan, tetapi tidak aman sebagai identitas baris karena kunci meta duplikat valid. Gunakan kunci utama tabel sebagai identitas baris, kecuali aplikasi host dapat membuktikan bahwa kunci alami tersebut unik.
Pengodean kunci baris
Kunci baris adalah string berkode yang menggabungkan beberapa nilai kolom dengan pemisah unit (\x1F). Nilai yang berisi pemisah atau tanda persen di-escape untuk mencegah tabrakan:
use Merql\Snapshot\Snapshotter;
// Encode.
$key = Snapshotter::buildRowKey(
['tenant_id' => '1', 'user_id' => '99'],
['tenant_id', 'user_id'],
);
// "1\x1F99"
// Decode.
$parts = Snapshotter::decodeRowKey($key);
// ['1', '99']
Aturan escape:
| Asli | Di-escape |
|---|---|
% |
%25 |
\x1F (pemisah unit) |
%1F |
Ini memastikan nilai yang berisi \x1F tidak terpecah menjadi beberapa bagian kunci. Pengodean ini deterministik dan dapat dibalik.
Perbedaan auto-increment
Saat kedua sisi menyisipkan baris baru, ID auto-increment akan berbeda antar cabang. Baris #100 di sisi kita mungkin merupakan catatan yang sama sekali berbeda dari baris #100 di sisi mereka.
merql menangani hal ini dengan mencocokkan berdasarkan kunci utama. Jika kedua sisi menyisipkan baris dengan ID auto-increment yang sama, hal itu ditandai sebagai konflik insert_insert. Untuk menghindarinya, gunakan kunci alami (kolom unik yang stabil antar cabang) pada tabel yang diperkirakan menerima penyisipan secara bersamaan.
// A natural key on (slug) avoids auto-increment collisions.
$identity = new NaturalKeyIdentity(['slug']);
Identitas dalam snapshot
Kolom identitas yang dipilih saat pengambilan disimpan bersama setiap TableSnapshot:
$tableSnapshot = $snapshot->getTable('posts');
$tableSnapshot->identityColumns;
// ['id']
Kolom-kolom ini digunakan oleh SqlGenerator untuk menyusun klausa WHERE bagi pernyataan UPDATE dan DELETE, serta oleh rencana merge untuk menampilkan nilai identitas yang telah didekode untuk ditinjau, dipilih, diterapkan dengan pengamanan, dan di-rollback.