Lewati ke konten

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.

PHP
use Merql\Identity\PrimaryKeyIdentity;

$identity = new PrimaryKeyIdentity(['id']);
$key = $identity->key(['id' => 42, 'title' => 'Hello']);
// "42"

Antarmuka RowIdentity

Semua strategi identitas mengimplementasikan dua metode:

PHP
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.

PHP
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.

PHP
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.

PHP
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.

PHP
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:

  1. Jika tabel memiliki kunci utama, gunakan kolom kunci utama.
  2. Jika tabel memiliki kunci unik, gunakan kolom dari kunci unik pertama.
  3. 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.

PHP
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:

PHP
use Merql\Merql;

Merql::init($connection, identityRules: $rules);
Merql::snapshot('live', ['wp_options']);

Kumpulan aturan memiliki serialisasi array yang stabil:

PHP
$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.

PHP
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:

PHP
$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:

PHP
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.

PHP
// 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:

PHP
$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.