Treci la conținut

Identitatea rândului

Identitatea rândurilor determină modul în care merql potrivește rândurile între instantanee. Când compară baza cu ours, trebuie să știe că rândul X din bază este același rând logic ca rândul Y din ours, chiar dacă unele valori ale coloanelor s-au schimbat. Regula de identitate stabilește ce coloane formează cheia rândului.

PHP
use Merql\Identity\PrimaryKeyIdentity;

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

Interfața RowIdentity

Toate strategiile de identitate implementează două metode:

PHP
interface RowIdentity
{
    public function key(array $row): string;
    public function columns(): array;
}

key() construiește un identificator unic sub formă de șir din datele unui rând. columns() returnează numele coloanelor folosite pentru identitate.

PrimaryKeyIdentity

Strategia implicită. Potrivește rândurile după coloanele cheii primare.

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)

Aceasta este strategia folosită implicit de merql atunci când schema tabelului definește o cheie primară.

NaturalKeyIdentity

Pentru tabelele fără cheie primară cu incrementare automată, potrivește rândurile după coloanele cu constrângeri unice. Este utilă când PK-ul este un UUID sau când tabelul folosește o constrângere unică compozită drept identitate logică.

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

Soluție de rezervă pentru tabelele fără cheie primară și fără constrângeri unice. Calculează un hash SHA-256 al valorilor tuturor coloanelor. Două rânduri sunt identice dacă toate coloanele corespund.

PHP
use Merql\Identity\ContentHashIdentity;

$identity = new ContentHashIdentity(['name', 'value']);
$key = $identity->key(['name' => 'color', 'value' => 'blue']);
// "a3f2..." (SHA-256 hash)

Identitatea bazată pe hash-ul de conținut are o limitare: dacă se schimbă conținutul unui rând, se schimbă și hash-ul său, astfel încât rândul apare ca ștergere a hash-ului vechi și inserare a hash-ului nou, nu ca actualizare. Acesta este un compromis inerent atunci când nu există coloane de identitate stabile.

IdentityRule

IdentityRule este configurația de identitate serializabilă folosită de capturarea instantaneelor și de planurile de îmbinare.

PHP
use Merql\Identity\IdentityRule;

$rule = IdentityRule::natural(['option_name']);
$key = $rule->key(['option_name' => 'siteurl']);
// "siteurl"

Tipuri de reguli:

Tip Semnificație
primary Coloanele cheii primare din baza de date
natural Coloane de afaceri unice și stabile
composite Coloane de identitate logică compozită
content Soluție de rezervă bazată pe hash-ul de conținut

IdentityRule::forSchema($schema) rezolvă regula implicită:

  1. Dacă tabelul are o cheie primară, folosiți coloanele cheii primare.
  2. Dacă tabelul are chei unice, folosiți coloanele primei chei unice.
  3. Dacă nu există niciuna, folosiți toate coloanele (soluție de rezervă bazată pe hash-ul de conținut).

IdentityRuleSet

Folosiți IdentityRuleSet atunci când o aplicație gazdă cunoaște o regulă de identitate mai potrivită decât valoarea implicită din schemă.

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 rezolvă o IdentityRule pentru fiecare tabel capturat. Coloanele alese sunt stocate în TableSnapshot::identityColumns și folosite pentru toate operațiunile ulterioare de diff, merge, plan, SQL și rollback.

Merql::init() poate primi același set de reguli când se folosește interfața statică:

PHP
use Merql\Merql;

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

Seturile de reguli au o serializare stabilă de tip tablou:

PHP
$payload = $rules->toArray();
$rules = IdentityRuleSet::fromArray($payload);

Identitate ambiguă

Dacă două rânduri capturate produc aceeași cheie de identitate, capturarea eșuează în loc să suprascrie un rând cu altul.

PHP
use Merql\Exceptions\SnapshotException;

try {
    $snapshotter->capture('live', ['wp_postmeta']);
} catch (SnapshotException $e) {
    // The configured identity rule matched multiple rows.
}

Aplicațiile gazdă pot verifica în prealabil rândurile brute cu IdentityRuleSet::conflictsFor() când vor să semnaleze ambiguitatea înainte de capturare:

PHP
$conflicts = $rules->conflictsFor(
    'wp_postmeta',
    IdentityRule::natural(['post_id', 'meta_key']),
    $rows,
);

Pentru tabelele meta WordPress, post_id + meta_key este de obicei util pentru grupare și afișare, dar nu este sigur ca identitate de rând, deoarece sunt permise chei meta duplicate. Folosiți cheia primară a tabelului pentru identitatea rândului, cu excepția cazului în care aplicația gazdă poate demonstra că cheia naturală este unică.

Codificarea cheii rândului

Cheile rândurilor sunt șiruri codificate care combină mai multe valori de coloană folosind un separator de unitate (\x1F). Valorile care conțin separatorul sau semnul procentului sunt escapate pentru a preveni coliziunile:

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']

Reguli de escapare:

Original Escapat
% %25
\x1F (separator de unitate) %1F

Astfel, o valoare care conține \x1F nu se împarte în mai multe părți ale cheii. Codificarea este deterministă și reversibilă.

Divergența valorilor cu incrementare automată

Când ambele părți inserează rânduri noi, ID-urile cu incrementare automată vor diferi între ramuri. Rândul #100 din ours ar putea fi o înregistrare complet diferită de rândul #100 din theirs.

merql gestionează acest lucru potrivind rândurile după cheia primară. Dacă ambele părți au inserat un rând cu același ID cu incrementare automată, acesta este marcat drept conflict insert_insert. Pentru a evita situația, folosiți chei naturale (coloane unice și stabile între ramuri) pentru tabelele în care sunt așteptate inserări concurente.

PHP
// A natural key on (slug) avoids auto-increment collisions.
$identity = new NaturalKeyIdentity(['slug']);

Identitatea în instantanee

Coloanele de identitate alese în timpul capturării sunt stocate împreună cu fiecare TableSnapshot:

PHP
$tableSnapshot = $snapshot->getTable('posts');
$tableSnapshot->identityColumns;
// ['id']

SqlGenerator folosește aceste coloane pentru a construi clauze WHERE pentru instrucțiunile UPDATE și DELETE, iar planurile de îmbinare le folosesc pentru a expune valorile de identitate decodificate în vederea examinării, selectării, aplicării cu verificări și revenirii.