
# Identiteta vrstice

Identiteta vrstice določa, kako merql povezuje vrstice med posnetki. Pri primerjavi osnovne različice z našo mora vedeti, da je vrstica X v osnovni različici ista logična vrstica kot vrstica Y v naši, tudi če so se nekatere vrednosti stolpcev spremenile. Pravilo identitete določa, kateri stolpci sestavljajo ključ vrstice.

```php
use Merql\Identity\PrimaryKeyIdentity;

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

## Vmesnik RowIdentity

Vse strategije identitete implementirajo dve metodi:

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

`key()` iz podatkov vrstice izdela enolični identifikator v obliki niza. `columns()` vrne imena stolpcev, uporabljenih za identifikacijo.

## PrimaryKeyIdentity

Privzeta strategija. Poveže vrstice glede na njihove stolpce primarnega ključ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)
```

To je privzeta strategija, ki jo uporablja merql, kadar ima tabela v shemi določen primarni ključ.

## NaturalKeyIdentity

Pri tabelah brez primarnega ključa s samodejnim povečevanjem poveže vrstice glede na stolpce omejitve enoličnosti. Uporabno, kadar je PK UUID ali kadar tabela kot logično identiteto uporablja sestavljeno omejitev enoličnosti.

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

Rezervna možnost za tabele brez primarnega ključa in omejitev enoličnosti. Izračuna zgoščeno vrednost SHA-256 vseh vrednosti stolpcev. Vrstici sta enaki, če se ujemajo vsi stolpci.

```php
use Merql\Identity\ContentHashIdentity;

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

Identifikacija z zgoščeno vrednostjo vsebine ima omejitev: če se vsebina vrstice spremeni, se spremeni tudi njena zgoščena vrednost, zato je vrstica prikazana kot izbris stare zgoščene vrednosti in vstavljanje nove, namesto kot posodobitev. To je neizogiben kompromis, kadar ni stabilnih stolpcev identitete.

## IdentityRule

`IdentityRule` je serializabilna konfiguracija identitete, ki jo uporabljata zajem posnetka
in načrti združevanja.

```php
use Merql\Identity\IdentityRule;

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

Vrste pravil:

| Vrsta | Pomen |
|---|---|
| `primary` | Stolpci primarnega ključa podatkovne zbirke |
| `natural` | Stabilni enolični poslovni stolpci |
| `composite` | Stolpci sestavljene logične identitete |
| `content` | Rezervna možnost z zgoščeno vrednostjo vsebine |

`IdentityRule::forSchema($schema)` določi privzeto pravilo:

1. Če ima tabela primarni ključ, uporabi stolpce primarnega ključa.
2. Če ima tabela enolične ključe, uporabi stolpce prvega enoličnega ključa.
3. Če nima nobenega od njih, uporabi vse stolpce (rezervna možnost z zgoščeno vrednostjo vsebine).

## IdentityRuleSet

Uporabite `IdentityRuleSet`, kadar gostiteljska aplikacija pozna boljše pravilo identitete kot
privzeto pravilo sheme.

```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` za vsako zajeto tabelo določi `IdentityRule`.
Izbrani stolpci so shranjeni v `TableSnapshot::identityColumns` in uporabljeni pri vseh
nadaljnjih operacijah diff, merge, plan, SQL in rollback.

`Merql::init()` lahko prejme isti nabor pravil tudi pri uporabi statičnega vmesnika:

```php
use Merql\Merql;

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

Nabori pravil imajo stabilno serializacijo polj:

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

## Neenolična identiteta

Če imata dve zajeti vrstici isti ključ identitete, zajem ne uspe, namesto da bi
eno vrstico prepisal z drugo.

```php
use Merql\Exceptions\SnapshotException;

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

Gostiteljske aplikacije lahko pred zajemom preverijo surove vrstice z `IdentityRuleSet::conflictsFor()`,
če želijo neenoličnost zaznati še pred zajemom:

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

Za WordPress-jeve metatabele je `post_id + meta_key` običajno uporaben za združevanje v skupine
in prikaz, ni pa varen za identifikacijo vrstic, ker so podvojeni ključi meta veljavni.
Za identiteto vrstice uporabite primarni ključ tabele, razen če lahko gostiteljska
aplikacija dokaže, da je naravni ključ enoličen.

## Kodiranje ključa vrstice

Ključi vrstic so kodirani nizi, ki združujejo vrednosti več stolpcev z ločilom enote (`\x1F`). Vrednosti, ki vsebujejo ločilo ali znak za odstotek, so ubežno kodirane, da se preprečijo kolizije:

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

Pravila ubežnega kodiranja:

| Izvirnik | Ubežano |
|---|---|
| `%` | `%25` |
| `\x1F` (ločilo enot) | `%1F` |

To zagotavlja, da se vrednost, ki vsebuje `\x1F`, ne razdeli na več delov ključa. Kodiranje je deterministično in reverzibilno.

## Razhajanje samodejnega povečevanja

Ko obe strani vstavita nove vrstice, se ID-ji samodejnega povečevanja med vejami razlikujejo. Vrstica #100 v naši kopiji je lahko povsem drug zapis kot vrstica #100 v njihovi kopiji.

merql to obravnava tako, da primerja primarne ključe. Če obe strani vstavita vrstico z istim ID-jem samodejnega povečevanja, je označena kot spor `insert_insert`. Da bi se temu izognili, za tabele, v katere se pričakujejo sočasni vnosi, uporabite naravne ključe (enolične stolpce, ki so stabilni med vejami).

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

## Identiteta v posnetkih

Stolpci identitete, izbrani med zajemom, so shranjeni z vsakim `TableSnapshot`:

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

Ti stolpci se uporabljajo za `SqlGenerator` za tvorjenje stavkov WHERE za UPDATE in DELETE, načrti združevanja pa z njimi prikažejo dekodirane vrednosti identitete za pregled, izbiro, varovano uporabo in povrnitev.
