
# 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.

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

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

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

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

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.

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

```php
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łę:

1. Jeśli tabela ma klucz główny, użyj jego kolumn.
2. Jeśli tabela ma klucze unikatowe, użyj kolumn pierwszego takiego klucza.
3. 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.

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

```php
use Merql\Merql;

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

Zestawy reguł mają stabilną serializację tablicową:

```php
$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.

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

```php
$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:

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

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.

```php
// 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`:

```php
$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.
