
# Identita riadka

Identita riadka určuje, ako merql páruje riadky naprieč snímkami. Pri porovnávaní základu s našou verziou musí vedieť, že riadok X v základe je ten istý logický riadok ako riadok Y v našej verzii, aj keď sa hodnoty niektorých stĺpcov zmenili. Pravidlo identity určuje, ktoré stĺpce tvoria kľúč riadka.

```php
use Merql\Identity\PrimaryKeyIdentity;

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

## Rozhranie RowIdentity

Všetky stratégie identity implementujú dve metódy:

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

`key()` vytvorí z údajov riadka jedinečný textový identifikátor. `columns()` vráti názvy stĺpcov použitých na určenie identity.

## PrimaryKeyIdentity

Predvolená stratégia. Páruje riadky podľa stĺpcov ich primárneho kľúča.

```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)
```

Túto stratégiu používa merql predvolene, ak má tabuľka v schéme definovaný primárny kľúč.

## NaturalKeyIdentity

V tabuľkách bez primárneho kľúča s automatickým zvyšovaním páruje riadky podľa stĺpcov s obmedzením jedinečnosti. Hodí sa, keď je PK UUID alebo keď tabuľka používa zložené obmedzenie jedinečnosti ako svoju logickú identitu.

```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

Náhradné riešenie pre tabuľky bez primárneho kľúča a obmedzení jedinečnosti. Vypočíta hash SHA-256 všetkých hodnôt stĺpcov. Dva riadky sú rovnaké, ak sa zhodujú všetky stĺpce.

```php
use Merql\Identity\ContentHashIdentity;

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

Identita založená na hashi obsahu má obmedzenie: ak sa obsah riadka zmení, zmení sa aj jeho hash, takže riadok sa namiesto aktualizácie javí ako odstránenie starého hashu a vloženie nového. Ide o nevyhnutný kompromis, keď nie sú k dispozícii stabilné identifikačné stĺpce.

## IdentityRule

`IdentityRule` je serializovateľná konfigurácia identity používaná pri zachytávaní snímok
 a v plánoch zlúčenia.

```php
use Merql\Identity\IdentityRule;

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

Typy pravidiel:

| Typ | Význam |
|---|---|
| `primary` | Stĺpce primárneho kľúča databázy |
| `natural` | Stabilné jedinečné obchodné stĺpce |
| `composite` | Stĺpce zloženého logického identifikátora |
| `content` | Záložná možnosť: hash obsahu |

`IdentityRule::forSchema($schema)` určuje predvolené pravidlo:

1. Ak má tabuľka primárny kľúč, použijú sa jeho stĺpce.
2. Ak má tabuľka jedinečné kľúče, použijú sa stĺpce prvého z nich.
3. Ak neexistuje ani jeden, použijú sa všetky stĺpce (záložná možnosť: hash obsahu).

## IdentityRuleSet

Použite `IdentityRuleSet`, keď hostiteľská aplikácia pozná vhodnejšie pravidlo identity než
predvolené pravidlo schémy.

```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` určuje `IdentityRule` pre každú zachytenú tabuľku. Vybrané
stĺpce sa ukladajú do `TableSnapshot::identityColumns` a používajú sa pri všetkých
nasledujúcich operáciách porovnávania rozdielov, zlúčenia, plánovania, SQL a návratu zmien.

`Merql::init()` môže pri použití statického rozhrania dostať rovnakú množinu pravidiel:

```php
use Merql\Merql;

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

Množiny pravidiel majú stabilnú serializáciu polí:

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

## Nejednoznačná identita

Ak dva zachytené riadky vytvoria rovnaký identifikačný kľúč, zachytenie zlyhá namiesto toho, aby jeden riadok prepísalo druhým.

```php
use Merql\Exceptions\SnapshotException;

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

Hostiteľské aplikácie môžu pred zachytením overiť nespracované riadky pomocou `IdentityRuleSet::conflictsFor()`,
ak chcú nejednoznačnosť oznámiť vopred:

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

V prípade metatabuliek WordPress je `post_id + meta_key` zvyčajne užitočné na zoskupovanie
a zobrazenie, ale nie je bezpečné ako identita riadka, pretože duplicitné kľúče metaúdajov sú platné.
Na identifikáciu riadka použite primárny kľúč tabuľky, pokiaľ hostiteľská aplikácia nedokáže,
že prirodzený kľúč je jedinečný.

## Kódovanie kľúča riadka

Kľúče riadkov sú zakódované reťazce, ktoré spájajú hodnoty viacerých stĺpcov oddeľovačom jednotiek (`\x1F`). Hodnoty obsahujúce oddeľovač alebo znak percenta sa escapujú, aby sa predišlo kolíziám:

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

Pravidlá escapovania:

| Pôvodná hodnota | Escapovaná hodnota |
|---|---|
| `%` | `%25` |
| `\x1F` (oddeľovač jednotiek) | `%1F` |

Tým sa zabezpečí, že hodnota obsahujúca `\x1F` sa nerozdelí na viacero častí kľúča. Kódovanie je deterministické a reverzibilné.

## Rozdiely v automatickom inkrementovaní

Keď obe strany vložia nové riadky, automaticky inkrementované ID sa budú medzi vetvami líšiť. Riadok #100 u nás môže byť úplne iný záznam než riadok #100 u nich.

merql to rieši párovaním podľa primárneho kľúča. Ak obe strany vložia riadok s rovnakým automaticky inkrementovaným ID, označí sa to ako konflikt `insert_insert`. Aby ste tomu predišli, v tabuľkách, kde sa očakávajú súbežné vkladania, používajte prirodzené kľúče (jedinečné stĺpce, ktoré sú stabilné naprieč vetvami).

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

## Identita v snímkach

Stĺpce identity vybrané pri zachytávaní sa ukladajú spolu s každým `TableSnapshot`:

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

Pomocou týchto stĺpcov vytvára `SqlGenerator` klauzuly WHERE pre príkazy UPDATE a DELETE a plány zlúčenia z nich poskytujú dekódované hodnoty identity na kontrolu, výber, podmienené vykonanie a návrat zmien.
