Към съдържанието

Идентичност на реда

Идентичността на реда определя как merql съпоставя редове в различни моментни снимки. При сравняване на base с ours трябва да знае, че редът X в base е същият логически ред като ред Y в ours, дори ако стойностите в някои колони са се променили. Правилото за идентичност определя кои колони образуват ключа на реда.

PHP
use Merql\Identity\PrimaryKeyIdentity;

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

Интерфейсът RowIdentity

Всички стратегии за идентичност реализират два метода:

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

key() създава уникален низов идентификатор от данните на реда. columns() връща имената на колоните, използвани за идентичност.

PrimaryKeyIdentity

Стратегията по подразбиране. Съпоставя редовете по колоните на първичния им ключ.

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)

Това използва merql по подразбиране, когато таблицата има дефиниран първичен ключ в схемата си.

NaturalKeyIdentity

За таблици без автоматично увеличаващ се първичен ключ съпоставя редовете по колоните с уникално ограничение. Полезно е, когато PK е UUID или таблицата използва съставно уникално ограничение като логическа идентичност.

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

Резервен вариант за таблици без първичен ключ и уникални ограничения. Изчислява SHA-256 хеш на стойностите във всички колони. Два реда са еднакви, ако стойността във всяка колона съвпада.

PHP
use Merql\Identity\ContentHashIdentity;

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

Идентичността чрез хеш на съдържанието има ограничение: ако съдържанието на реда се промени, се променя и хешът му. Затова редът изглежда като изтриване на стария хеш и вмъкване на новия, вместо като актуализация. Това е неизбежният компромис, когато няма стабилни колони за идентичност.

IdentityRule

IdentityRule е сериализируемата конфигурация за идентичност, използвана при заснемане на моментни снимки и изготвяне на планове за сливане.

PHP
use Merql\Identity\IdentityRule;

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

Типове правила:

Тип Значение
primary Колони на първичния ключ на базата данни
natural Стабилни уникални бизнес колони
composite Колони на съставната логическа идентичност
content Резервен вариант с хеш на съдържанието

IdentityRule::forSchema($schema) разрешава правилото по подразбиране:

  1. Ако таблицата има първичен ключ, използвайте колоните на първичния ключ.
  2. Ако таблицата има уникални ключове, използвайте колоните на първия уникален ключ.
  3. Ако няма нито едното, използвайте всички колони (резервен вариант с хеш на съдържанието).

IdentityRuleSet

Използвайте IdentityRuleSet, когато хост приложението познава по-подходящо правило за идентичност от това по подразбиране в схемата.

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 разрешава IdentityRule за всяка заснета таблица. Избраните колони се съхраняват в TableSnapshot::identityColumns и се използват за всички последващи операции за сравнение, сливане, планиране, SQL и връщане назад.

Merql::init() може да получава същия набор от правила и при използване на статичния фасаден интерфейс:

PHP
use Merql\Merql;

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

Наборите от правила имат стабилна сериализация на масиви:

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

Нееднозначна идентичност

Ако два заснети реда получат един и същ ключ за идентичност, заснемането се проваля, вместо единият ред да презапише другия.

PHP
use Merql\Exceptions\SnapshotException;

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

Хост приложенията могат предварително да проверят необработени редове с IdentityRuleSet::conflictsFor(), когато искат да съобщят за нееднозначност преди заснемането:

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

За мета таблици на WordPress post_id + meta_key обикновено е полезен за групиране и показване, но не е безопасен като идентичност на ред, защото дублиращите се мета ключове са допустими. Използвайте първичния ключ на таблицата за идентичност на реда, освен ако хост приложението не може да докаже, че естественият ключ е уникален.

Кодиране на ключовете на редовете

Ключовете на редовете са кодирани низове, които комбинират стойности от няколко колони с разделител на единици (\x1F). Стойностите, съдържащи разделителя или знака за процент, се екранират, за да се предотвратят колизии:

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

Правила за екраниране:

Оригинал Екраниран
% %25
\x1F (разделител на единици) %1F

Така се гарантира, че стойност, съдържаща \x1F, няма да се раздели на няколко части на ключа. Кодирането е детерминистично и обратимо.

Разминаване при автоматичното нарастване

Когато и двете страни вмъкват нови редове, автоматично нарастващите идентификатори ще се различават между клоновете. Ред #100 в нашия клон може да е напълно различен запис от ред #100 в техния.

merql решава това чрез съпоставяне по първичен ключ. Ако и двете страни вмъкнат ред със същия автоматично нарастващ идентификатор, той се маркира като конфликт insert_insert. За да избегнете това, използвайте естествени ключове (уникални колони, които остават стабилни между клоновете) за таблици, в които се очакват едновременни вмъквания.

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

Идентичност в моментните снимки

Колоните за идентичност, избрани при заснемането, се съхраняват с всеки TableSnapshot:

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

Тези колони се използват от SqlGenerator за изграждане на клаузи WHERE за операторите UPDATE и DELETE, както и от плановете за сливане, за да показват декодираните стойности за идентичност при преглед, избор, защитено прилагане и връщане назад.