Идентичност на реда
Идентичността на реда определя как merql съпоставя редове в различни моментни снимки. При сравняване на base с ours трябва да знае, че редът X в base е същият логически ред като ред Y в ours, дори ако стойностите в някои колони са се променили. Правилото за идентичност определя кои колони образуват ключа на реда.
use Merql\Identity\PrimaryKeyIdentity;
$identity = new PrimaryKeyIdentity(['id']);
$key = $identity->key(['id' => 42, 'title' => 'Hello']);
// "42"
Интерфейсът RowIdentity
Всички стратегии за идентичност реализират два метода:
interface RowIdentity
{
public function key(array $row): string;
public function columns(): array;
}
key() създава уникален низов идентификатор от данните на реда. columns() връща имената на колоните, използвани за идентичност.
PrimaryKeyIdentity
Стратегията по подразбиране. Съпоставя редовете по колоните на първичния им ключ.
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)
Това използва merql по подразбиране, когато таблицата има дефиниран първичен ключ в схемата си.
NaturalKeyIdentity
За таблици без автоматично увеличаващ се първичен ключ съпоставя редовете по колоните с уникално ограничение. Полезно е, когато PK е UUID или таблицата използва съставно уникално ограничение като логическа идентичност.
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
Резервен вариант за таблици без първичен ключ и уникални ограничения. Изчислява SHA-256 хеш на стойностите във всички колони. Два реда са еднакви, ако стойността във всяка колона съвпада.
use Merql\Identity\ContentHashIdentity;
$identity = new ContentHashIdentity(['name', 'value']);
$key = $identity->key(['name' => 'color', 'value' => 'blue']);
// "a3f2..." (SHA-256 hash)
Идентичността чрез хеш на съдържанието има ограничение: ако съдържанието на реда се промени, се променя и хешът му. Затова редът изглежда като изтриване на стария хеш и вмъкване на новия, вместо като актуализация. Това е неизбежният компромис, когато няма стабилни колони за идентичност.
IdentityRule
IdentityRule е сериализируемата конфигурация за идентичност, използвана при заснемане на моментни снимки
и изготвяне на планове за сливане.
use Merql\Identity\IdentityRule;
$rule = IdentityRule::natural(['option_name']);
$key = $rule->key(['option_name' => 'siteurl']);
// "siteurl"
Типове правила:
| Тип | Значение |
|---|---|
primary |
Колони на първичния ключ на базата данни |
natural |
Стабилни уникални бизнес колони |
composite |
Колони на съставната логическа идентичност |
content |
Резервен вариант с хеш на съдържанието |
IdentityRule::forSchema($schema) разрешава правилото по подразбиране:
- Ако таблицата има първичен ключ, използвайте колоните на първичния ключ.
- Ако таблицата има уникални ключове, използвайте колоните на първия уникален ключ.
- Ако няма нито едното, използвайте всички колони (резервен вариант с хеш на съдържанието).
IdentityRuleSet
Използвайте IdentityRuleSet, когато хост приложението познава по-подходящо правило за идентичност от това по подразбиране в схемата.
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 разрешава IdentityRule за всяка заснета таблица. Избраните колони се съхраняват в TableSnapshot::identityColumns и се използват за всички последващи операции за сравнение, сливане, планиране, SQL и връщане назад.
Merql::init() може да получава същия набор от правила и при използване на статичния фасаден интерфейс:
use Merql\Merql;
Merql::init($connection, identityRules: $rules);
Merql::snapshot('live', ['wp_options']);
Наборите от правила имат стабилна сериализация на масиви:
$payload = $rules->toArray();
$rules = IdentityRuleSet::fromArray($payload);
Нееднозначна идентичност
Ако два заснети реда получат един и същ ключ за идентичност, заснемането се проваля, вместо единият ред да презапише другия.
use Merql\Exceptions\SnapshotException;
try {
$snapshotter->capture('live', ['wp_postmeta']);
} catch (SnapshotException $e) {
// The configured identity rule matched multiple rows.
}
Хост приложенията могат предварително да проверят необработени редове с IdentityRuleSet::conflictsFor(), когато искат да съобщят за нееднозначност преди заснемането:
$conflicts = $rules->conflictsFor(
'wp_postmeta',
IdentityRule::natural(['post_id', 'meta_key']),
$rows,
);
За мета таблици на WordPress post_id + meta_key обикновено е полезен за групиране и показване, но не е безопасен като идентичност на ред, защото дублиращите се мета ключове са допустими. Използвайте първичния ключ на таблицата за идентичност на реда, освен ако хост приложението не може да докаже, че естественият ключ е уникален.
Кодиране на ключовете на редовете
Ключовете на редовете са кодирани низове, които комбинират стойности от няколко колони с разделител на единици (\x1F). Стойностите, съдържащи разделителя или знака за процент, се екранират, за да се предотвратят колизии:
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']
Правила за екраниране:
| Оригинал | Екраниран |
|---|---|
% |
%25 |
\x1F (разделител на единици) |
%1F |
Така се гарантира, че стойност, съдържаща \x1F, няма да се раздели на няколко части на ключа. Кодирането е детерминистично и обратимо.
Разминаване при автоматичното нарастване
Когато и двете страни вмъкват нови редове, автоматично нарастващите идентификатори ще се различават между клоновете. Ред #100 в нашия клон може да е напълно различен запис от ред #100 в техния.
merql решава това чрез съпоставяне по първичен ключ. Ако и двете страни вмъкнат ред със същия автоматично нарастващ идентификатор, той се маркира като конфликт insert_insert. За да избегнете това, използвайте естествени ключове (уникални колони, които остават стабилни между клоновете) за таблици, в които се очакват едновременни вмъквания.
// A natural key on (slug) avoids auto-increment collisions.
$identity = new NaturalKeyIdentity(['slug']);
Идентичност в моментните снимки
Колоните за идентичност, избрани при заснемането, се съхраняват с всеки TableSnapshot:
$tableSnapshot = $snapshot->getTable('posts');
$tableSnapshot->identityColumns;
// ['id']
Тези колони се използват от SqlGenerator за изграждане на клаузи WHERE за операторите UPDATE и DELETE, както и от плановете за сливане, за да показват декодираните стойности за идентичност при преглед, избор, защитено прилагане и връщане назад.