Identita řádku
Identita řádku určuje, jak merql páruje řádky napříč snímky. Při porovnávání base s ours potřebuje vědět, že řádek X v base je tentýž logický řádek jako řádek Y v ours, i když se hodnoty některých sloupců změnily. Pravidlo identity určuje, které sloupce tvoří klíč řádku.
use Merql\Identity\PrimaryKeyIdentity;
$identity = new PrimaryKeyIdentity(['id']);
$key = $identity->key(['id' => 42, 'title' => 'Hello']);
// "42"
Rozhraní RowIdentity
Všechny strategie identity implementují dvě metody:
interface RowIdentity
{
public function key(array $row): string;
public function columns(): array;
}
key() vytvoří jedinečný řetězcový identifikátor z dat řádku. columns() vrátí názvy sloupců použitých k určení identity.
PrimaryKeyIdentity
Výchozí strategie. Páruje řádky podle sloupců primárního klíče.
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)
Tuto strategii merql standardně používá, pokud má tabulka ve schématu definovaný primární klíč.
NaturalKeyIdentity
U tabulek bez automaticky navyšovaného primárního klíče páruje řádky podle sloupců s jedinečným omezením. Hodí se, když je PK UUID nebo když tabulka používá složené jedinečné omezení jako svou logickou identitu.
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
Záložní strategie pro tabulky bez primárního klíče i jedinečných omezení. Vypočítá hash SHA-256 ze všech hodnot sloupců. Dva řádky jsou stejné, pokud se shodují ve všech sloupcích.
use Merql\Identity\ContentHashIdentity;
$identity = new ContentHashIdentity(['name', 'value']);
$key = $identity->key(['name' => 'color', 'value' => 'blue']);
// "a3f2..." (SHA-256 hash)
Identita založená na otisku obsahu má omezení: pokud se obsah řádku změní, změní se i jeho hash, takže se řádek místo aktualizace zobrazí jako odstranění starého hashe a vložení nového. Jde o nevyhnutelný kompromis, pokud nejsou k dispozici stabilní identifikační sloupce.
IdentityRule
IdentityRule je serializovatelná konfigurace identity používaná při pořizování snímku a přípravě plánů sloučení.
use Merql\Identity\IdentityRule;
$rule = IdentityRule::natural(['option_name']);
$key = $rule->key(['option_name' => 'siteurl']);
// "siteurl"
Typy pravidel:
| Typ | Význam |
|---|---|
primary |
Sloupce primárního klíče databáze |
natural |
Stabilní jedinečné obchodní sloupce |
composite |
Sloupce složené logické identity |
content |
Záložní otisk obsahu |
IdentityRule::forSchema($schema) vybere výchozí pravidlo:
- Pokud má tabulka primární klíč, použijí se jeho sloupce.
- Pokud má tabulka jedinečné klíče, použijí se sloupce prvního z nich.
- Pokud nemá ani jedno, použijí se všechny sloupce (záložní otisk obsahu).
IdentityRuleSet
Použijte IdentityRuleSet, když hostitelská aplikace zná lepší pravidlo identity než výchozí pravidlo schématu.
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 vybere IdentityRule pro každou zaznamenanou tabulku. Vybrané sloupce se uloží do TableSnapshot::identityColumns a použijí se při všech následných operacích diff, merge, plan, SQL a rollback.
Merql::init() může při použití statického rozhraní dostat stejnou sadu pravidel:
use Merql\Merql;
Merql::init($connection, identityRules: $rules);
Merql::snapshot('live', ['wp_options']);
Sady pravidel mají stabilní serializaci polí:
$payload = $rules->toArray();
$rules = IdentityRuleSet::fromArray($payload);
Nejednoznačná identita
Pokud dva zaznamenané řádky vytvoří stejný klíč identity, pořizování snímku selže, místo aby jeden řádek přepsal druhým.
use Merql\Exceptions\SnapshotException;
try {
$snapshotter->capture('live', ['wp_postmeta']);
} catch (SnapshotException $e) {
// The configured identity rule matched multiple rows.
}
Hostitelské aplikace mohou před pořizováním snímku ověřit nezpracované řádky pomocí IdentityRuleSet::conflictsFor() a včas oznámit nejednoznačnost:
$conflicts = $rules->conflictsFor(
'wp_postmeta',
IdentityRule::natural(['post_id', 'meta_key']),
$rows,
);
U metatabulWordPress je post_id + meta_key obvykle užitečné pro seskupování a zobrazení, ale není bezpečné jako identita řádku, protože duplicitní meta klíče jsou povolené. Pro identitu řádku použijte primární klíč tabulky, pokud hostitelská aplikace nemůže zaručit jedinečnost přirozeného klíče.
Kódování klíče řádku
Klíče řádků jsou zakódované řetězce, které kombinují hodnoty více sloupců pomocí oddělovače jednotek (\x1F). Hodnoty obsahující oddělovač nebo znak procenta se escapují, aby se předešlo kolizím:
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']
Pravidla escapování:
| Původní | Escapované |
|---|---|
% |
%25 |
\x1F (oddělovač jednotek) |
%1F |
Tím se zajistí, že se hodnota obsahující \x1F nerozdělí na více částí klíče. Kódování je deterministické a reverzibilní.
Rozdíly v automaticky navyšovaných ID
Když obě strany vloží nové řádky, automaticky navyšovaná ID se mezi větvemi budou lišit. Řádek #100 v ours může být zcela jiný záznam než řádek #100 v theirs.
merql tento problém řeší párováním podle primárního klíče. Pokud obě strany vložily řádek se stejným automaticky navyšovaným ID, označí jej jako konflikt insert_insert. Tomu lze předejít použitím přirozených klíčů (jedinečných sloupců, které jsou stabilní napříč větvemi) u tabulek, kde se očekává souběžné vkládání.
// A natural key on (slug) avoids auto-increment collisions.
$identity = new NaturalKeyIdentity(['slug']);
Identita ve snímcích
Sloupce identity vybrané při pořizování snímku se ukládají spolu s každým TableSnapshot:
$tableSnapshot = $snapshot->getTable('posts');
$tableSnapshot->identityColumns;
// ['id']
SqlGenerator tyto sloupce používá k sestavení klauzulí WHERE pro příkazy UPDATE a DELETE. Plány sloučení je využívají k zobrazení dekódovaných hodnot identity pro kontrolu, výběr, řízené použití a vrácení změn.