Tożsamość wiersza
Tożsamość wiersza określa, w jaki sposób merql dopasowuje wiersze w różnych migawkach. Porównując base z ours, trzeba ustalić, że wiersz X w base jest tym samym wierszem logicznym co wiersz Y w ours, nawet jeśli zmieniły się wartości niektórych kolumn. Reguła tożsamości określa, które kolumny tworzą klucz wiersza.
use Merql\Identity\PrimaryKeyIdentity;
$identity = new PrimaryKeyIdentity(['id']);
$key = $identity->key(['id' => 42, 'title' => 'Hello']);
// "42"
Interfejs RowIdentity
Każda strategia określania tożsamości implementuje dwie metody:
interface RowIdentity
{
public function key(array $row): string;
public function columns(): array;
}
key() tworzy unikatowy identyfikator tekstowy z danych wiersza. columns() zwraca nazwy kolumn używane do określenia tożsamości.
PrimaryKeyIdentity
Domyślna strategia. Dopasowuje wiersze według ich kolumn klucza głównego.
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)
To domyślna strategia merql, gdy tabela ma klucz główny zdefiniowany w schemacie.
NaturalKeyIdentity
W tabelach bez automatycznie zwiększanego klucza głównego dopasowuje wiersze według kolumn ograniczenia unikatowości. Przydatne, gdy PK to UUID lub gdy tabela używa złożonego ograniczenia unikatowości jako swojej tożsamości logicznej.
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
Strategia zastępcza dla tabel bez klucza głównego i ograniczeń unikatowości. Oblicza skrót SHA-256 ze wszystkich wartości kolumn. Dwa wiersze są takie same, jeśli wszystkie kolumny mają identyczne wartości.
use Merql\Identity\ContentHashIdentity;
$identity = new ContentHashIdentity(['name', 'value']);
$key = $identity->key(['name' => 'color', 'value' => 'blue']);
// "a3f2..." (SHA-256 hash)
Tożsamość oparta na skrócie zawartości ma pewne ograniczenie: jeśli zawartość wiersza się zmieni, zmieni się też jego skrót. Wiersz zostanie wtedy uznany za usunięcie starego skrótu i dodanie nowego zamiast aktualizacji. Jest to nieodłączny kompromis, gdy brak stabilnych kolumn tożsamości.
IdentityRule
IdentityRule to możliwa do serializacji konfiguracja tożsamości używana podczas tworzenia migawki
oraz przez plany scalania.
use Merql\Identity\IdentityRule;
$rule = IdentityRule::natural(['option_name']);
$key = $rule->key(['option_name' => 'siteurl']);
// "siteurl"
Typy reguł:
| Typ | Znaczenie |
|---|---|
primary |
Kolumny klucza głównego bazy danych |
natural |
Stabilne, unikatowe kolumny biznesowe |
composite |
Kolumny złożonej tożsamości logicznej |
content |
Zastępczy skrót zawartości |
IdentityRule::forSchema($schema) wybiera domyślną regułę:
- Jeśli tabela ma klucz główny, użyj jego kolumn.
- Jeśli tabela ma klucze unikatowe, użyj kolumn pierwszego takiego klucza.
- Jeśli tabela nie ma żadnego z nich, użyj wszystkich kolumn (zastępczy skrót zawartości).
IdentityRuleSet
Użyj IdentityRuleSet, gdy aplikacja nadrzędna zna lepszą regułę tożsamości niż
domyślna reguła schematu.
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 określa IdentityRule dla każdej zarejestrowanej tabeli. Wybrane kolumny są zapisywane w TableSnapshot::identityColumns i używane we wszystkich kolejnych operacjach różnicowania, scalania, planowania, SQL i wycofywania.
Merql::init() może otrzymać ten sam zestaw reguł przy korzystaniu ze statycznej fasady:
use Merql\Merql;
Merql::init($connection, identityRules: $rules);
Merql::snapshot('live', ['wp_options']);
Zestawy reguł mają stabilną serializację tablicową:
$payload = $rules->toArray();
$rules = IdentityRuleSet::fromArray($payload);
Niejednoznaczna tożsamość
Jeśli dwa zarejestrowane wiersze utworzą ten sam klucz tożsamości, tworzenie migawki zakończy się błędem, zamiast nadpisać jeden wiersz drugim.
use Merql\Exceptions\SnapshotException;
try {
$snapshotter->capture('live', ['wp_postmeta']);
} catch (SnapshotException $e) {
// The configured identity rule matched multiple rows.
}
Aplikacje nadrzędne mogą wstępnie sprawdzić surowe wiersze za pomocą IdentityRuleSet::conflictsFor(),
aby zgłosić niejednoznaczność przed utworzeniem migawki:
$conflicts = $rules->conflictsFor(
'wp_postmeta',
IdentityRule::natural(['post_id', 'meta_key']),
$rows,
);
W przypadku tabel meta WordPress post_id + meta_key zwykle przydaje się do grupowania
i wyświetlania, ale nie jest bezpieczna jako tożsamość wiersza, ponieważ powtarzające się klucze meta są dozwolone.
Do określania tożsamości wiersza używaj klucza głównego tabeli, chyba że aplikacja nadrzędna
może potwierdzić unikatowość klucza naturalnego.
Kodowanie klucza wiersza
Klucze wierszy to zakodowane ciągi łączące wartości wielu kolumn separatorem jednostek (\x1F). Wartości zawierające separator lub znak procentu są kodowane, aby zapobiec kolizjom:
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']
Zasady kodowania:
| Oryginał | Po zakodowaniu |
|---|---|
% |
%25 |
\x1F (separator jednostek) |
%1F |
Dzięki temu wartość zawierająca \x1F nie zostanie podzielona na wiele części klucza. Kodowanie jest deterministyczne i odwracalne.
Rozbieżności liczników automatycznych
Gdy obie strony dodają nowe wiersze, identyfikatory z liczników automatycznych różnią się między gałęziami. Wiersz #100 w ours może być zupełnie innym rekordem niż wiersz #100 w theirs.
merql rozwiązuje ten problem, dopasowując wiersze według klucza głównego. Jeśli obie strony dodały wiersz z tym samym identyfikatorem z licznika automatycznego, zostaje on oznaczony jako konflikt insert_insert. Aby tego uniknąć, używaj kluczy naturalnych (stabilnych między gałęziami, unikatowych kolumn) w tabelach, w których można się spodziewać równoczesnego dodawania wierszy.
// A natural key on (slug) avoids auto-increment collisions.
$identity = new NaturalKeyIdentity(['slug']);
Tożsamość w migawkach
Kolumny tożsamości wybrane podczas tworzenia migawki są zapisywane w każdym TableSnapshot:
$tableSnapshot = $snapshot->getTable('posts');
$tableSnapshot->identityColumns;
// ['id']
SqlGenerator używa tych kolumn do tworzenia klauzul WHERE w instrukcjach UPDATE i DELETE. Plany scalania wykorzystują je do udostępniania zdekodowanych wartości tożsamości na potrzeby przeglądu, wyboru, kontrolowanego zastosowania i wycofywania zmian.