Rij-identiteit
Rij-identiteit bepaalt hoe merql rijen aan elkaar koppelt in verschillende snapshots. Bij het vergelijken van base met ours moet het weten dat rij X in base dezelfde logische rij is als rij Y in ours, ook als sommige kolomwaarden zijn gewijzigd. De identiteitsregel bepaalt welke kolommen de rijsleutel vormen.
use Merql\Identity\PrimaryKeyIdentity;
$identity = new PrimaryKeyIdentity(['id']);
$key = $identity->key(['id' => 42, 'title' => 'Hello']);
// "42"
De RowIdentity-interface
Alle identiteitsstrategieën implementeren twee methoden:
interface RowIdentity
{
public function key(array $row): string;
public function columns(): array;
}
key() bouwt een unieke tekenreeksidentificatie op basis van de gegevens van een rij. columns() retourneert de kolomnamen die voor de identiteit worden gebruikt.
PrimaryKeyIdentity
De standaardstrategie. Koppelt rijen op basis van hun primaire-sleutelkolommen.
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)
Dit gebruikt merql standaard wanneer de tabel een primaire sleutel heeft die in het schema is gedefinieerd.
NaturalKeyIdentity
Voor tabellen zonder automatisch oplopende primaire sleutel worden rijen gekoppeld op basis van kolommen met unieke beperkingen. Handig wanneer de PK een UUID is of wanneer de tabel een samengestelde unieke beperking gebruikt als logische identiteit.
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
Terugvaloptie voor tabellen zonder primaire sleutel of unieke beperkingen. Berekent een SHA-256-hash van alle kolomwaarden. Twee rijen zijn hetzelfde als elke kolom overeenkomt.
use Merql\Identity\ContentHashIdentity;
$identity = new ContentHashIdentity(['name', 'value']);
$key = $identity->key(['name' => 'color', 'value' => 'blue']);
// "a3f2..." (SHA-256 hash)
Identiteit op basis van een inhoudshash heeft een beperking: als de inhoud van een rij verandert, verandert ook de hash. De rij verschijnt dan als verwijdering van de oude hash en invoeging van de nieuwe hash, in plaats van als update. Dit is een onvermijdelijk compromis wanneer er geen stabiele identiteitskolommen zijn.
IdentityRule
IdentityRule is de serialiseerbare identiteitsconfiguratie die wordt gebruikt voor het vastleggen van snapshots
en mergeplannen.
use Merql\Identity\IdentityRule;
$rule = IdentityRule::natural(['option_name']);
$key = $rule->key(['option_name' => 'siteurl']);
// "siteurl"
Regeltypen:
| Type | Betekenis |
|---|---|
primary |
Primaire-sleutelkolommen van de database |
natural |
Stabiele unieke bedrijfsgegevenskolommen |
composite |
Samengestelde kolommen voor logische identiteit |
content |
Terugvaloptie op basis van inhoudshash |
IdentityRule::forSchema($schema) bepaalt de standaardregel:
- Heeft de tabel een primaire sleutel, gebruik dan de kolommen van die sleutel.
- Heeft de tabel unieke sleutels, gebruik dan de kolommen van de eerste unieke sleutel.
- Is geen van beide aanwezig, gebruik dan alle kolommen (terugvaloptie op basis van inhoudshash).
IdentityRuleSet
Gebruik IdentityRuleSet wanneer een hosttoepassing een betere identiteitsregel kent dan
de standaardregel uit het schema.
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']);
De Snapshotter bepaalt een IdentityRule voor elke vastgelegde tabel. De
geselecteerde kolommen worden opgeslagen in TableSnapshot::identityColumns en gebruikt voor alle
volgende bewerkingen: diff, merge, plan, SQL en rollback.
Merql::init() kan dezelfde regelset ontvangen bij gebruik van de statische facade:
use Merql\Merql;
Merql::init($connection, identityRules: $rules);
Merql::snapshot('live', ['wp_options']);
Regelsets hebben een stabiele arrayserialisatie:
$payload = $rules->toArray();
$rules = IdentityRuleSet::fromArray($payload);
Ambigue identiteit
Als twee vastgelegde rijen dezelfde identiteitssleutel opleveren, mislukt het vastleggen in plaats van de ene rij door de andere te overschrijven.
use Merql\Exceptions\SnapshotException;
try {
$snapshotter->capture('live', ['wp_postmeta']);
} catch (SnapshotException $e) {
// The configured identity rule matched multiple rows.
}
Hosttoepassingen kunnen onbewerkte rijen vooraf controleren met IdentityRuleSet::conflictsFor()
om ambiguïteit vóór het vastleggen te melden:
$conflicts = $rules->conflictsFor(
'wp_postmeta',
IdentityRule::natural(['post_id', 'meta_key']),
$rows,
);
Voor metatabellen van WordPress is post_id + meta_key meestal nuttig om te groeperen
en weer te geven, maar niet veilig als rij-identiteit, omdat dubbele metasleutels geldig zijn.
Gebruik de primaire sleutel van de tabel voor rij-identiteit, tenzij de hosttoepassing
kan aantonen dat de natuurlijke sleutel uniek is.
Codering van rijsleutels
Rijsleutels zijn gecodeerde tekenreeksen waarin meerdere kolomwaarden worden gecombineerd met een scheidingsteken voor eenheid (\x1F). Waarden met het scheidingsteken of het procentteken worden ge-escaped om botsingen te voorkomen:
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']
Escaperegels:
| Oorspronkelijk | Ge-escaped |
|---|---|
% |
%25 |
\x1F (scheidingsteken voor eenheid) |
%1F |
Zo wordt voorkomen dat een waarde met \x1F in meerdere sleutelonderdelen wordt opgesplitst. De codering is deterministisch en omkeerbaar.
Verschillen in automatisch oplopende waarden
Wanneer beide zijden nieuwe rijen invoegen, verschillen de automatisch oplopende ID's tussen branches. Rij #100 in ours kan een heel ander record zijn dan rij #100 in theirs.
merql lost dit op door te koppelen op basis van de primaire sleutel. Als beide zijden een rij met dezelfde automatisch oplopende ID hebben ingevoegd, wordt dit aangemerkt als een conflict van het type insert_insert. Gebruik voor tabellen waarin gelijktijdige invoegingen worden verwacht natuurlijke sleutels (unieke kolommen die stabiel zijn tussen branches) om dit te voorkomen.
// A natural key on (slug) avoids auto-increment collisions.
$identity = new NaturalKeyIdentity(['slug']);
Identiteit in snapshots
De tijdens het vastleggen gekozen identiteitskolommen worden opgeslagen bij elke TableSnapshot:
$tableSnapshot = $snapshot->getTable('posts');
$tableSnapshot->identityColumns;
// ['id']
Deze kolommen worden door SqlGenerator gebruikt om WHERE-clausules voor UPDATE- en DELETE-statements op te bouwen. Mergeplannen gebruiken ze om gedecodeerde identiteitswaarden beschikbaar te maken voor beoordeling, selectie, beveiligde toepassing en rollback.