
# Eilučių tapatybė

Eilutės tapatybė nustato, kaip merql susieja eilutes skirtingose momentinėse kopijose. Lyginant base su ours, reikia nustatyti, kad eilutė X base yra ta pati loginė eilutė kaip Y eilutė ours, net jei pasikeitė kai kurios stulpelių reikšmės. Tapatybės taisyklė nurodo, kurie stulpeliai sudaro eilutės raktą.

```php
use Merql\Identity\PrimaryKeyIdentity;

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

## RowIdentity sąsaja

Visos tapatybės strategijos įgyvendina du metodus:

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

`key()` sukuria unikalų eilutės duomenų identifikatorių, o `columns()` grąžina tapatybei naudojamų stulpelių pavadinimus.

## PrimaryKeyIdentity

Numatytoji strategija. Eilutės susiejamos pagal pirminio rakto stulpelius.

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

Tai merql naudoja pagal numatytuosius nustatymus, kai lentelės schemoje apibrėžtas pirminis raktas.

## NaturalKeyIdentity

Jei lentelė neturi automatiškai didinamo pirminio rakto, eilutės susiejamos pagal unikalaus apribojimo stulpelius. Tinka, kai PK yra UUID arba kai lentelei kaip loginei tapatybei naudojamas sudėtinis unikalus apribojimas.

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

Atsarginė parinktis lentelėms be pirminio rakto ir unikalių apribojimų. Apskaičiuoja SHA-256 maišą iš visų stulpelių reikšmių. Dvi eilutės laikomos vienodomis, jei sutampa visi jų stulpeliai.

```php
use Merql\Identity\ContentHashIdentity;

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

Turinio maiša pagrįsta tapatybė turi trūkumą: pasikeitus eilutės turiniui, pasikeičia ir jos maiša, todėl eilutė atrodo kaip seno įrašo ištrynimas ir naujo įterpimas, o ne atnaujinimas. Tai neišvengiamas kompromisas, kai nėra stabilių tapatybės stulpelių.

## IdentityRule

`IdentityRule` yra nuosekliai serializuojama tapatybės konfigūracija, naudojama fiksuojant momentinę kopiją
ir sudarant sujungimo planus.

```php
use Merql\Identity\IdentityRule;

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

Taisyklių tipai:

| Tipas | Reikšmė |
|---|---|
| `primary` | Duomenų bazės pirminio rakto stulpeliai |
| `natural` | Stabilūs unikalūs verslo stulpeliai |
| `composite` | Sudėtiniai loginės tapatybės stulpeliai |
| `content` | Atsarginė turinio maišos parinktis |

`IdentityRule::forSchema($schema)` parenka numatytąją taisyklę:

1. Jei lentelė turi pirminį raktą, naudojami jo stulpeliai.
2. Jei lentelė turi unikalių raktų, naudojami pirmojo unikalaus rakto stulpeliai.
3. Jei nėra nė vieno iš jų, naudojami visi stulpeliai (atsarginė turinio maišos parinktis).

## IdentityRuleSet

Naudokite `IdentityRuleSet`, kai pagrindinė programa žino geresnę tapatybės taisyklę nei numatytoji schemos taisyklė.

```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` kiekvienai užfiksuotai lentelei parenka `IdentityRule`. Pasirinkti stulpeliai įrašomi į `TableSnapshot::identityColumns` ir naudojami visoms vėlesnėms diff, merge, plan, SQL ir rollback operacijoms.

Naudojant statinę sąsają, `Merql::init()` galima perduoti tą patį taisyklių rinkinį:

```php
use Merql\Merql;

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

Taisyklių rinkiniai serializuojami į stabilų masyvą:

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

## Dviprasmė tapatybė

Jei dvi užfiksuotos eilutės sukuria tą patį tapatybės raktą, fiksavimas nepavyksta, o viena eilutė neperrašo kitos.

```php
use Merql\Exceptions\SnapshotException;

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

Pagrindinės programos gali iš anksto patikrinti neapdorotas eilutes naudodamos `IdentityRuleSet::conflictsFor()`,
kad apie dviprasmybę praneštų dar prieš fiksavimą:

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

WordPress metaduomenų lentelėse `post_id + meta_key` paprastai naudinga grupavimui
ir rodymui, tačiau netinka kaip eilutės tapatybė, nes pasikartojantys metaduomenų raktai yra galimi.
Eilučių tapatybei naudokite lentelės pirminį raktą, nebent pagrindinė programa gali
įrodyti, kad natūralusis raktas yra unikalus.

## Eilučių raktų kodavimas

Eilučių raktai yra koduotos eilutės, kuriose kelios stulpelių reikšmės sujungiamos skyrikliu (`\x1F`). Reikšmės, kuriose yra skyriklis arba procento ženklas, užkoduojamos, kad būtų išvengta susidūrimų:

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

Kodavimo taisyklės:

| Pradinė reikšmė | Užkoduota reikšmė |
|---|---|
| `%` | `%25` |
| `\x1F` (skyriklis) | `%1F` |

Taip užtikrinama, kad reikšmė su `\x1F` nebūtų suskaidyta į kelias rakto dalis. Kodavimas yra deterministinis ir grįžtamasis.

## Automatinio didinimo neatitikimai

Kai abi pusės įterpia naujas eilutes, automatiškai didinami ID skirtingose šakose nesutampa. Eilutė #100 pusėje ours gali būti visiškai kitoks įrašas nei eilutė #100 pusėje theirs.

merql eilutes susieja pagal pirminį raktą. Jei abi pusės įterpia eilutę su tuo pačiu automatiškai didinamu ID, tai pažymima kaip `insert_insert` konfliktas. Kad to išvengtumėte, lentelėse, kuriose tikėtini vienalaikiai įterpimai, naudokite natūraliuosius raktus (unikalius, skirtingose šakose stabilius stulpelius).

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

## Tapatybė momentinėse kopijose

Fiksuojant pasirinktų tapatybės stulpelių reikšmės išsaugomos su kiekviena `TableSnapshot`:

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

`SqlGenerator` naudoja šiuos stulpelius UPDATE ir DELETE sakinių WHERE sąlygoms sudaryti, o sujungimo planai – dekoduotoms tapatybės reikšmėms pateikti peržiūrai, pasirinkimui, apsaugotam pritaikymui ir grąžinimui.
